From 3f2e68f7fa7aafb57dd5a0d240c4e48ccbf53650 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 13 Feb 2025 17:49:34 +0100 Subject: [PATCH 01/49] feat: use new file structure(wip) --- .eslintrc.yml | 2 +- docs/gitops.md | 8 +- package-lock.json | 125 ++- package.json | 4 +- src/api/teams/{teamId}/backups/{backupId}.ts | 8 +- src/api/teams/{teamId}/builds/{buildId}.ts | 8 +- src/api/teams/{teamId}/netpols/{netpolId}.ts | 8 +- .../teams/{teamId}/projects/{projectId}.ts | 10 +- .../{teamId}/sealedsecrets/{secretId}.ts | 8 +- src/api/teams/{teamId}/secrets.ts | 8 +- .../teams/{teamId}/services/{serviceId}.ts | 8 +- .../teams/{teamId}/workloads/{workloadId}.ts | 8 +- .../{teamId}/workloads/{workloadId}/values.ts | 4 +- src/app.ts | 27 +- src/git.ts | 402 +++++++++ src/middleware/authz.ts | 23 +- src/middleware/jwt.ts | 2 +- src/middleware/session.ts | 9 +- src/otomi-models.ts | 34 + src/otomi-stack.ts | 771 +++++++++--------- src/playground.ts | 17 + src/repo.ts | 718 ++++++++-------- src/services/RepoService.ts | 443 ++++++++++ src/services/TeamConfigService.ts | 392 +++++++++ src/utils/userUtils.ts | 8 +- 25 files changed, 2174 insertions(+), 881 deletions(-) create mode 100644 src/git.ts create mode 100644 src/playground.ts mode change 100644 => 100755 src/repo.ts create mode 100644 src/services/RepoService.ts create mode 100644 src/services/TeamConfigService.ts diff --git a/.eslintrc.yml b/.eslintrc.yml index 0e3f34210..04317f68d 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -60,7 +60,7 @@ rules: '@typescript-eslint/naming-convention': ['error', { 'selector': 'function', 'format': ['camelCase'], 'leadingUnderscore': 'allow' }] comma-dangle: [error, only-multiline] - curly: [error, multi-or-nest] +# curly: [error, multi-or-nest] eol-last: ['error', 'always'] func-names: off import/extensions: off diff --git a/docs/gitops.md b/docs/gitops.md index 64f2e8610..54d45788a 100644 --- a/docs/gitops.md +++ b/docs/gitops.md @@ -18,7 +18,7 @@ sequenceDiagram participant SC as Session Controller participant MC as Master Session Controller participant ACA as APL Core API - participant RR as Remote Repo + participant RR as Remote Git participant IMDB as In-Memory DB activate MC activate IMDB @@ -80,7 +80,7 @@ sequenceDiagram participant API as Express API participant SC as Session Controller participant MC as Master Session Controller - participant RR as Remote Repo + participant RR as Remote Git participant ACA as APL Core API participant IMDB as In-Memory DB activate MC @@ -131,7 +131,7 @@ sequenceDiagram participant Client as Client participant API as Express API participant SC as Git Handler - participant RR as Remote Repo + participant RR as Remote Git participant ACA as APL Core API participant IMDB as In-Memory DB activate IMDB @@ -179,7 +179,7 @@ sequenceDiagram participant Client as Client participant API as Express API participant SC as Git Handler - participant RR as Remote Repo + participant RR as Remote Git participant ACA as APL Core API Client->>API: HTTP POST/PUT/PATCH/DELETE activate API diff --git a/package-lock.json b/package-lock.json index 36b175b34..e90878aca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "glob": "^11.0.1", "http-signature": "^1.3.6", "json-stable-stringify": "^1.0.1", + "jsonpath": "^1.1.1", "jsonwebtoken": "^9.0.0", "jwt-decode": "^2.2.0", "lightship": "^6.7.2", @@ -102,8 +103,7 @@ "watch": "0.19.2" }, "engines": { - "node": ">=20 <21", - "npm": "^10" + "node": ">=20 <21" } }, "node_modules/@ampproject/remapping": { @@ -6567,8 +6567,7 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" }, "node_modules/deepmerge": { "version": "4.3.1", @@ -7159,6 +7158,78 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "license": "MIT", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "license": "MIT", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/eslint": { "version": "8.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", @@ -7730,7 +7801,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "engines": { "node": ">=4.0" } @@ -7739,7 +7809,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -8285,8 +8354,7 @@ "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "node_modules/fast-printf": { "version": "1.6.9", @@ -11560,6 +11628,17 @@ "node >= 0.2.0" ] }, + "node_modules/jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "license": "MIT", + "dependencies": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + } + }, "node_modules/jsonpath-plus": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", @@ -11577,6 +11656,18 @@ "node": ">=18.0.0" } }, + "node_modules/jsonpath/node_modules/esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -16210,7 +16301,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -16494,6 +16585,15 @@ "node": ">=10" } }, + "node_modules/static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "license": "MIT", + "dependencies": { + "escodegen": "^1.8.1" + } + }, "node_modules/static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -17848,6 +17948,12 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==", + "license": "MIT" + }, "node_modules/undici": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/undici/-/undici-5.1.1.tgz", @@ -18333,7 +18439,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, "engines": { "node": ">=0.10.0" } diff --git a/package.json b/package.json index 334c87273..ae96d477a 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "glob": "^11.0.1", "http-signature": "^1.3.6", "json-stable-stringify": "^1.0.1", + "jsonpath": "^1.1.1", "jsonwebtoken": "^9.0.0", "jwt-decode": "^2.2.0", "lightship": "^6.7.2", @@ -102,8 +103,7 @@ "watch": "0.19.2" }, "engines": { - "node": ">=20 <21", - "npm": "^10" + "node": ">=20 <21" }, "engineStrict": true, "homepage": "https://github.com/redkubes/otomi-api#readme", diff --git a/src/api/teams/{teamId}/backups/{backupId}.ts b/src/api/teams/{teamId}/backups/{backupId}.ts index 584f65ca2..37f9c1ae8 100644 --- a/src/api/teams/{teamId}/backups/{backupId}.ts +++ b/src/api/teams/{teamId}/backups/{backupId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:backups') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { backupId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, backupId } }: OpenApiRequestExt, res): Promise => { debug(`deleteBackup(${backupId})`) - await otomi.deleteBackup(decodeURIComponent(backupId)) + await otomi.deleteBackup(decodeURIComponent(teamId), decodeURIComponent(backupId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { backupId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, backupId } }: OpenApiRequestExt, res): void => { debug(`getBackup(${backupId})`) - const data = otomi.getBackup(decodeURIComponent(backupId)) + const data = otomi.getBackup(decodeURIComponent(teamId), decodeURIComponent(backupId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/builds/{buildId}.ts b/src/api/teams/{teamId}/builds/{buildId}.ts index 82429f1ed..7d321ef18 100644 --- a/src/api/teams/{teamId}/builds/{buildId}.ts +++ b/src/api/teams/{teamId}/builds/{buildId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:builds') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { buildId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, buildId } }: OpenApiRequestExt, res): Promise => { debug(`deleteBuild(${buildId})`) - await otomi.deleteBuild(decodeURIComponent(buildId)) + await otomi.deleteBuild(decodeURIComponent(teamId), decodeURIComponent(buildId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { buildId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, buildId } }: OpenApiRequestExt, res): void => { debug(`getBuild(${buildId})`) - const data = otomi.getBuild(decodeURIComponent(buildId)) + const data = otomi.getBuild(decodeURIComponent(teamId), decodeURIComponent(buildId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/netpols/{netpolId}.ts b/src/api/teams/{teamId}/netpols/{netpolId}.ts index 163e5c7ee..a161f7b1a 100644 --- a/src/api/teams/{teamId}/netpols/{netpolId}.ts +++ b/src/api/teams/{teamId}/netpols/{netpolId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:netpols') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { netpolId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, netpolId } }: OpenApiRequestExt, res): Promise => { debug(`deleteNetpol(${netpolId})`) - await otomi.deleteNetpol(decodeURIComponent(netpolId)) + await otomi.deleteNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { netpolId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, netpolId } }: OpenApiRequestExt, res): void => { debug(`getNetpol(${netpolId})`) - const data = otomi.getNetpol(decodeURIComponent(netpolId)) + const data = otomi.getNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/projects/{projectId}.ts b/src/api/teams/{teamId}/projects/{projectId}.ts index f8560176b..567d51097 100644 --- a/src/api/teams/{teamId}/projects/{projectId}.ts +++ b/src/api/teams/{teamId}/projects/{projectId}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:projects') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { projectId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, projectId } }: OpenApiRequestExt, res): Promise => { debug(`deleteProject(${projectId})`) - await otomi.deleteProject(decodeURIComponent(projectId)) + await otomi.deleteProject(decodeURIComponent(teamId), decodeURIComponent(projectId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { projectId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, projectId } }: OpenApiRequestExt, res): void => { debug(`getProject(${projectId})`) - const data = otomi.getProject(decodeURIComponent(projectId)) + const data = otomi.getProject(decodeURIComponent(teamId), decodeURIComponent(projectId)) res.json(data) }, ] const put: Operation = [ async ({ otomi, params: { teamId, projectId }, body }: OpenApiRequestExt, res): Promise => { debug(`editProject(${projectId})`) - const data = await otomi.editProject(decodeURIComponent(projectId), { + const data = await otomi.editProject(decodeURIComponent(teamId), decodeURIComponent(projectId), { ...body, teamId: decodeURIComponent(teamId), } as Project) diff --git a/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts b/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts index 370ef7fe7..d8cbf050d 100644 --- a/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts +++ b/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:sealedsecrets') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { secretId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, secretId } }: OpenApiRequestExt, res): Promise => { debug(`deleteSealedSecret(${secretId})`) - await otomi.deleteSealedSecret(decodeURIComponent(secretId)) + await otomi.deleteSealedSecret(decodeURIComponent(teamId), decodeURIComponent(secretId)) res.json({}) }, ] const get: Operation = [ - async ({ otomi, params: { secretId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, secretId } }: OpenApiRequestExt, res): Promise => { debug(`getSealedSecret(${secretId})`) - const data = await otomi.getSealedSecret(decodeURIComponent(secretId)) + const data = await otomi.getSealedSecret(decodeURIComponent(teamId), decodeURIComponent(secretId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/secrets.ts b/src/api/teams/{teamId}/secrets.ts index eeb6265a4..1fc5e0b5b 100644 --- a/src/api/teams/{teamId}/secrets.ts +++ b/src/api/teams/{teamId}/secrets.ts @@ -1,6 +1,6 @@ import Debug from 'debug' import { Operation, OperationHandlerArray } from 'express-openapi' -import { OpenApiRequestExt, Secret } from 'src/otomi-models' +import { OpenApiRequestExt } from 'src/otomi-models' const debug = Debug('otomi:api:teams:secrets') @@ -8,15 +8,13 @@ export default function (): OperationHandlerArray { const get: Operation = [ ({ otomi, params: { teamId } }: OpenApiRequestExt, res): void => { debug(`getSecrets(${teamId})`) - const v = otomi.getSecrets(teamId) - res.json(v) + res.json('not implemented') }, ] const post: Operation = [ ({ otomi, params: { teamId }, body }: OpenApiRequestExt, res): void => { debug(`createSecret(${teamId}, ...)`) - const v = otomi.createSecret(teamId, body as Secret) - res.json(v) + res.json('not implemented') }, ] const api = { diff --git a/src/api/teams/{teamId}/services/{serviceId}.ts b/src/api/teams/{teamId}/services/{serviceId}.ts index 5a7c5f1cc..d15e0defc 100644 --- a/src/api/teams/{teamId}/services/{serviceId}.ts +++ b/src/api/teams/{teamId}/services/{serviceId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:services') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { serviceId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, serviceId } }: OpenApiRequestExt, res): Promise => { debug(`deleteService(${serviceId})`) - await otomi.deleteService(decodeURIComponent(serviceId)) + await otomi.deleteService(decodeURIComponent(teamId), decodeURIComponent(serviceId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { serviceId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, serviceId } }: OpenApiRequestExt, res): void => { debug(`getService(${serviceId})`) - const data = otomi.getService(decodeURIComponent(serviceId)) + const data = otomi.getService(decodeURIComponent(teamId), decodeURIComponent(serviceId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/workloads/{workloadId}.ts b/src/api/teams/{teamId}/workloads/{workloadId}.ts index 64e469a42..3faa0a1df 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}.ts +++ b/src/api/teams/{teamId}/workloads/{workloadId}.ts @@ -6,16 +6,16 @@ const debug = Debug('otomi:api:teams:workloads') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { workloadId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): Promise => { debug(`deleteWorkload(${workloadId})`) - await otomi.deleteWorkload(decodeURIComponent(workloadId)) + await otomi.deleteWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { workloadId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): void => { debug(`getWorkload(${workloadId})`) - const data = otomi.getWorkload(decodeURIComponent(workloadId)) + const data = otomi.getWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId)) res.json(data) }, ] diff --git a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts b/src/api/teams/{teamId}/workloads/{workloadId}/values.ts index ba2b2e47f..ae87713d6 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts +++ b/src/api/teams/{teamId}/workloads/{workloadId}/values.ts @@ -6,9 +6,9 @@ const debug = Debug('otomi:api:teams:workloadValues') export default function (): OperationHandlerArray { const get: Operation = [ - ({ otomi, params: { workloadId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): void => { debug(`getWorkloadValues(${workloadId})`) - const data = otomi.getWorkloadValues(decodeURIComponent(workloadId)) + const data = otomi.getWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadId)) res.json(data) }, ] diff --git a/src/app.ts b/src/app.ts index 79c374028..ade0a7c64 100644 --- a/src/app.ts +++ b/src/app.ts @@ -13,8 +13,8 @@ import logger from 'morgan' import path from 'path' import { default as Authz } from 'src/authz' import { - DbMessage, authzMiddleware, + DbMessage, errorMiddleware, getIo, getSessionStack, @@ -22,19 +22,18 @@ import { sessionMiddleware, } from 'src/middleware' import { setMockIdx } from 'src/mocks' -import { Build, OpenAPIDoc, OpenApiRequestExt, Schema, SealedSecret, Service, Workload } from 'src/otomi-models' +import { OpenAPIDoc, OpenApiRequestExt, Schema } from 'src/otomi-models' import { default as OtomiStack } from 'src/otomi-stack' import { extract, getPaths, getValuesSchema } from 'src/utils' import { CHECK_LATEST_COMMIT_INTERVAL, + cleanEnv, DRONE_WEBHOOK_SECRET, EXPRESS_PAYLOAD_LIMIT, GIT_PASSWORD, GIT_USER, - cleanEnv, } from 'src/validators' import swaggerUi from 'swagger-ui-express' -import Db from './db' import giteaCheckLatest from './gitea/connect' import { getBuildStatus, getSealedSecretStatus, getServiceStatus, getWorkloadStatus } from './k8s_operations' @@ -63,13 +62,13 @@ const checkAgainstGitea = async () => { const latestOtomiVersion = await giteaCheckLatest(encodedToken, clusterInfo) // check the local version against the latest online version // if the latest online is newer it will be pulled locally - if (latestOtomiVersion && latestOtomiVersion.data[0].sha !== otomiStack.repo.commitSha) { + if (latestOtomiVersion && latestOtomiVersion.data[0].sha !== otomiStack.git.commitSha) { debug('Local values differentiate from Git repository, retrieving latest values') - await otomiStack.repo.pull() + await otomiStack.git.pull() // inflate new db - otomiStack.db = new Db() + await otomiStack.initRepo() await otomiStack.loadValues() - const sha = await otomiStack.repo.getCommitSha() + const sha = await otomiStack.git.getCommitSha() const msg: DbMessage = { state: 'clean', editor: 'system', sha, reason: 'conflict' } getIo().emit('db', msg) } @@ -80,10 +79,10 @@ const resourceStatus = async (errorSet) => { const { cluster } = otomiStack.getSettings(['cluster']) const domainSuffix = cluster?.domainSuffix const resources = { - workloads: otomiStack.db.getCollection('workloads') as Array, - builds: otomiStack.db.getCollection('builds') as Array, - services: otomiStack.db.getCollection('services') as Array, - sealedSecrets: otomiStack.db.getCollection('sealedsecrets') as Array, + workloads: otomiStack.repoService.getAllWorkloads(), + builds: otomiStack.repoService.getAllBuilds(), + services: otomiStack.repoService.getAllServices(), + sealedSecrets: otomiStack.repoService.getAllSealedSecrets(), } const statusFunctions = { workloads: getWorkloadStatus, @@ -176,7 +175,7 @@ export async function initApp(inOtomiStack?: OtomiStack | undefined) { if (status === 'success') { const stack = await getSessionStack() debug('Drone deployed, root pull') - await stack.repo.pull() + await stack.git.pull() } }) let server: Server | undefined @@ -188,7 +187,7 @@ export async function initApp(inOtomiStack?: OtomiStack | undefined) { debug(`Listening on :::${PORT}`) lightship.signalReady() // Clone repo after the application is ready to avoid Pod NotReady phenomenon, and thus infinite Pod crash loopback - ;(await getSessionStack()).initRepo() + ;(await getSessionStack()).initGit() }) .on('error', (e) => { console.error(e) diff --git a/src/git.ts b/src/git.ts new file mode 100644 index 000000000..146ba3e53 --- /dev/null +++ b/src/git.ts @@ -0,0 +1,402 @@ +import axios, { AxiosResponse } from 'axios' +import Debug from 'debug' +import diff from 'deep-diff' +import { copy, ensureDir, pathExists, readFile, writeFile } from 'fs-extra' +import { unlink } from 'fs/promises' +import stringifyJson from 'json-stable-stringify' +import { cloneDeep, get, isEmpty, merge, set, unset } from 'lodash' +import { dirname, join } from 'path' +import simpleGit, { CheckRepoActions, CleanOptions, CommitResult, ResetMode, SimpleGit } from 'simple-git' +import { cleanEnv, GIT_BRANCH, GIT_LOCAL_PATH, GIT_PASSWORD, GIT_REPO_URL, GIT_USER, TOOLS_HOST } from 'src/validators' +import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' +import { BASEURL } from './constants' +import { GitPullError, HttpError, ValidationError } from './error' +import { DbMessage, getIo } from './middleware' +import { Core } from './otomi-models' +import { removeBlankAttributes } from './utils' + +const debug = Debug('otomi:repo') + +const env = cleanEnv({ + GIT_BRANCH, + GIT_LOCAL_PATH, + GIT_PASSWORD, + GIT_REPO_URL, + GIT_USER, + TOOLS_HOST, +}) + +const baseUrl = BASEURL +const prepareUrl = `${baseUrl}/prepare` +const initUrl = `${baseUrl}/init` +const valuesUrl = `${baseUrl}/otomi/values` + +const getProtocol = (url): string => (url && url.includes('://') ? url.split('://')[0] : 'https') + +const getUrl = (url): string => (!url || url.includes('://') ? url : `${getProtocol(url)}://${url}`) + +function getUrlAuth(url, user, password): string | undefined { + if (!url) return + const protocol = getProtocol(url) + const [_, bareUrl] = url.split('://') + const encodedUser = encodeURIComponent(user as string) + const encodedPassword = encodeURIComponent(password as string) + return protocol === 'file' ? `${protocol}://${bareUrl}` : `${protocol}://${encodedUser}:${encodedPassword}@${bareUrl}` +} + +const secretFileRegex = new RegExp(`^(.*/)?secrets.*.yaml(.dec)?$`) +export class Git { + branch: string + commitSha: string + corrupt = false + email: string + git: SimpleGit + password: string + path: string + remote: string + remoteBranch: string + secretFilePostfix = '' + url: string | undefined + urlAuth: string | undefined + user: string + + constructor( + path: string, + url: string | undefined, + user: string, + email: string, + urlAuth: string | undefined, + branch: string | undefined, + ) { + this.branch = branch || 'main' + this.email = email + this.path = path + this.remote = 'origin' + this.remoteBranch = join(this.remote, this.branch) + this.urlAuth = urlAuth + this.user = user + this.url = url + this.git = simpleGit(this.path) + } + + getProtocol() { + return getProtocol(this.url) + } + + async requestInitValues(): Promise { + debug(`Tools: requesting "init" on values repo path ${this.path}`) + const res = await axios.get(initUrl, { params: { envDir: this.path } }) + return res + } + + async requestPrepareValues(): Promise { + debug(`Tools: requesting "prepare" on values repo path ${this.path}`) + const res = await axios.get(prepareUrl, { params: { envDir: this.path } }) + return res + } + + async requestValues(params): Promise { + debug(`Tools: requesting "otomi/values" ${this.path}`) + const res = await axios.get(valuesUrl, { params: { envDir: this.path, ...params } }) + return res + } + async addConfig(): Promise { + debug(`Adding git config`) + await this.git.addConfig('user.name', this.user) + await this.git.addConfig('user.email', this.email) + if (this.isRootClone()) { + if (this.getProtocol() === 'file') { + // tell the the git repo there to accept updates even when it is checked out + const _git = simpleGit(this.url!.replace('file://', '')) + await _git.addConfig('receive.denyCurrentBranch', 'updateInstead') + } + // same for the root repo, which needs to accept pushes from children + await this.git.addConfig('receive.denyCurrentBranch', 'updateInstead') + } + } + + async init(bare = true): Promise { + await this.git.init(bare !== undefined ? bare : this.isRootClone()) + await this.git.addRemote(this.remote, this.url!) + } + + async initSops(): Promise { + if (this.secretFilePostfix === '.dec') return + this.secretFilePostfix = (await pathExists(join(this.path, '.sops.yaml'))) ? '.dec' : '' + } + + getSafePath(file: string): string { + if (this.secretFilePostfix === '') return file + // otherwise we might have to give *.dec variant for secrets + if (file.match(secretFileRegex) && !file.endsWith(this.secretFilePostfix)) return `${file}${this.secretFilePostfix}` + return file + } + + async removeFile(file: string): Promise { + const absolutePath = join(this.path, file) + const exists = await this.fileExists(file) + if (exists) { + debug(`Removing file: ${absolutePath}`) + // Remove empty secret file due to https://github.com/mozilla/sops/issues/926 issue + await unlink(absolutePath) + } + if (file.match(secretFileRegex)) { + // also remove the encrypted file as they are operated on in pairs + const encFile = `${file}${this.secretFilePostfix}` + if (await this.fileExists(encFile)) { + const absolutePathEnc = join(this.path, encFile) + debug(`Removing enc file: ${absolutePathEnc}`) + await unlink(absolutePathEnc) + } + } + } + + async diffFile(file: string, data: Record): Promise { + const repoFile: string = this.getSafePath(file) + const oldData = await this.readFile(repoFile) + const deepDiff = diff(data, oldData) + debug(`Diff for ${file}: `, deepDiff) + return deepDiff + } + + async writeFile(file: string, data: Record, unsetBlankAttributes = true): Promise { + let cleanedData = data + if (unsetBlankAttributes) cleanedData = removeBlankAttributes(data, { emptyArrays: true }) + if (isEmpty(cleanedData) && file.match(secretFileRegex)) { + // remove empty secrets file which sops can't handle + return this.removeFile(file) + } + // we also bail when no changes found + const hasDiff = await this.diffFile(file, data) + if (!hasDiff) return + // ok, write new content + const absolutePath = join(this.path, file) + debug(`Writing to file: ${absolutePath}`) + const sortedData = JSON.parse(stringifyJson(data) as string) + const content = isEmpty(sortedData) ? '' : stringifyYaml(sortedData, undefined, 4) + const dir = dirname(absolutePath) + await ensureDir(dir) + await writeFile(absolutePath, content, 'utf8') + } + + async fileExists(relativePath: string): Promise { + const absolutePath = join(this.path, relativePath) + return await pathExists(absolutePath) + } + + async readFile(file: string, checkSuffix = false): Promise> { + if (!(await this.fileExists(file))) return {} + const safeFile = checkSuffix ? this.getSafePath(file) : file + const absolutePath = join(this.path, safeFile) + debug(`Reading from file: ${absolutePath}`) + const doc = parseYaml(await readFile(absolutePath, 'utf8')) || {} + return doc + } + + async loadConfig(file: string, secretFile: string): Promise { + const data = await this.readFile(file) + const secretData = await this.readFile(secretFile, true) + return merge(data, secretData) as Core + } + + async saveConfig( + dataPath: string, + inSecretRelativeFilePath: string, + config: Record, + secretJsonPaths: string[], + ): Promise> { + const secretData = {} + const plainData = cloneDeep(config) + secretJsonPaths.forEach((objectPath) => { + const val = get(config, objectPath) + if (val) { + set(secretData, objectPath, val) + unset(plainData, objectPath) + } + }) + + let secretDataRelativePath = `${inSecretRelativeFilePath}${this.secretFilePostfix}` + + if (this.secretFilePostfix) { + const secretExists = await this.fileExists(inSecretRelativeFilePath) + // In case secret file does not exists, create new one and let sops to encrypt it in place + if (!secretExists) secretDataRelativePath = inSecretRelativeFilePath + } + + await this.writeFile(secretDataRelativePath, secretData) + await this.writeFile(dataPath, plainData) + } + + isRootClone(): boolean { + return this.path === env.GIT_LOCAL_PATH + } + + hasRemote(): boolean { + return !!env.GIT_REPO_URL + } + + async initFromTestFolder(): Promise { + // we inflate GIT_LOCAL_PATH from the ./test folder + debug(`DEV mode: using local folder values`) + await copy(join(process.cwd(), 'test'), env.GIT_LOCAL_PATH, { + recursive: true, + overwrite: false, + }) + await this.init(false) + await this.git.checkoutLocalBranch(this.branch) + await this.git.add('.') + await this.addConfig() + await this.git.commit('initial commit', undefined, this.getOptions()) + } + + async clone(): Promise { + debug(`Checking if local git repository exists at: ${this.path}`) + const isRepo = await this.git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT) + // remote root url + this.url = getUrl(`${env.GIT_REPO_URL}`) + if (!isRepo) { + debug(`Initializing repo...`) + if (!this.hasRemote() && this.isRootClone()) return await this.initFromTestFolder() + else if (!this.isRootClone()) { + // child clone, point to remote root + this.urlAuth = getUrlAuth(this.url, env.GIT_USER, env.GIT_PASSWORD) + } + debug(`Cloning from '${this.url}' to '${this.path}'`) + await this.git.clone(this.urlAuth!, this.path) + await this.addConfig() + await this.git.checkout(this.branch) + } else if (this.url) { + debug('Repo already exists. Checking out correct branch.') + // Git fetch ensures that local git repository is synced with remote repository + await this.git.fetch({}) + await this.git.checkout(this.branch) + } + this.commitSha = await this.getCommitSha() + } + + getOptions() { + const options = {} + if (env.isDev) options['--no-verify'] = null // only for dev do we have git hooks blocking direct commit + return options + } + + async commit(editor: string): Promise { + await this.git.add('./*') + const summary = await this.git.commit(`otomi-api commit by ${editor}`, undefined, this.getOptions()) + debug(`Commit summary: ${JSON.stringify(summary)}`) + return summary + } + + async pull(skipRequest = false, skipMsg = false): Promise { + // test root can't pull as it has no remote + if (!this.url) return + debug('Pulling') + try { + const summary = await this.git.pull(this.remote, this.branch, { '--rebase': 'true' }) + const summJson = JSON.stringify(summary) + debug(`Pull summary: ${summJson}`) + this.commitSha = await this.getCommitSha() + if (!skipRequest) await this.requestInitValues() + await this.initSops() + } catch (e) { + debug('Could not pull from remote. Upstream commits? Marked db as corrupt.', e) + this.corrupt = true + if (!skipMsg) { + const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'conflict' } + getIo().emit('db', msg) + } + try { + // Remove local changes so that no conflict can happen + debug('Removing local changes.') + await this.git.reset(ResetMode.HARD) + debug(`Go to ${this.branch} branch`) + await this.git.checkout(this.branch) + debug('Removing local changes.') + await this.git.reset(ResetMode.HARD) + debug('Cleaning local values and directories.') + await this.git.clean(CleanOptions.FORCE, ['-d']) + debug('Get the latest branch from:', this.remote) + await this.git.fetch(this.remote, this.branch) + debug('Reconciling divergent branches.') + await this.git.merge([`${this.remote}/${this.branch}`, '--strategy-option=theirs']) + debug('Trying to remove upstream commits: ', this.remote) + await this.git.push([this.remote, this.branch, '--force']) + } catch (error) { + debug('Failed to remove upstream commits: ', error) + throw new GitPullError('Failed to remove upstream commits!') + } + debug('Removed upstream commits!') + const cleanMsg: DbMessage = { editor: 'system', state: 'clean', reason: 'restored' } + getIo().emit('db', cleanMsg) + this.corrupt = false + } + } + + async push(): Promise { + if (!this.url && this.isRootClone()) return + debug('Pushing') + const summary = await this.git.push(this.remote, this.branch) + debug('Pushed. Summary: ', summary) + return + } + + async getCommitSha(): Promise { + return this.git.revparse('HEAD') + } + + async save(editor: string): Promise { + // prepare values first + try { + await this.requestPrepareValues() + } catch (e) { + debug(`ERROR: ${JSON.stringify(e)}`) + if (e.response) { + const { status } = e.response as AxiosResponse + if (status === 422) throw new ValidationError() + throw HttpError.fromCode(status) + } + throw new HttpError(500, `${e}`) + } + // all good? commit + await this.commit(editor) + try { + // we are in a unique developer branch, so we can pull, push, and merge + // with the remote root, which might have been modified by another developer + // since this is a child branch, we don't need to re-init + // retry up to 3 times to pull and push if there are conflicts + const skipInit = true + const retries = 3 + for (let attempt = 1; attempt <= retries; attempt++) { + try { + await this.pull(skipInit) + await this.push() + break + } catch (error) { + if (attempt === retries) throw error + debug(`Attempt ${attempt} failed. Retrying...`) + } + } + } catch (e) { + debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) + debug(`Merge error: ${JSON.stringify(e)}`) + throw new GitPullError() + } + } +} + +export default async function getRepo( + path: string, + url: string, + user: string, + email: string, + password: string, + branch: string, + method: 'clone' | 'init' = 'clone', +): Promise { + await ensureDir(path, { mode: 0o744 }) + const urlNormalized = getUrl(url) + const urlAuth = getUrlAuth(urlNormalized, user, password) + const repo = new Git(path, urlNormalized, user, email, urlAuth, branch) + await repo[method]() + return repo +} diff --git a/src/middleware/authz.ts b/src/middleware/authz.ts index 6375e93ea..28ef28b6f 100644 --- a/src/middleware/authz.ts +++ b/src/middleware/authz.ts @@ -2,11 +2,13 @@ import { RequestHandler } from 'express' import get from 'lodash/get' import Authz, { getTeamSelfServiceAuthz } from 'src/authz' -import Db from 'src/db' import { OpenApiRequestExt, PermissionSchema, TeamSelfService } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' import { cleanEnv } from 'src/validators' import { getSessionStack } from './session' +import { RepoService } from '../services/RepoService' +import { debug } from 'console' +import { find } from 'lodash' const HttpMethodMapping: Record = { DELETE: 'delete', @@ -38,7 +40,7 @@ function renameKeys(obj: Record) { // } // } -export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, db: Db): RequestHandler { +export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoService: RepoService): RequestHandler { const { params: { teamId }, body, @@ -71,23 +73,26 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, db: D } } - const schemaToDbMap: Record = { - Secret: 'secrets', + const schemaToRepoMap: Record = { Service: 'services', - Team: 'teams', + Team: 'teamConfig', } const selector = renameKeys(req.params) - + const collectionId = schemaToRepoMap[schemaName] if (['create', 'update'].includes(action)) { - const collection = schemaToDbMap[schemaName] let dataOrig = get( req, `apiDoc.components.schemas.TeamSelfService.properties.${schemaName.toLowerCase()}.x-allow-values`, {}, ) - if (action === 'update') dataOrig = db.getItemReference(collection, selector, false) as Record + try { + const collection = repoService.getCollection(collectionId) + dataOrig = find(collection, selector) || {} + } catch (error) { + debug('Error in authzMiddleware', error) + } const violatedAttributes = authz.validateWithAbac(action, schemaName, teamId, req.body, dataOrig) if (violatedAttributes.length > 0) { return res.status(403).send({ @@ -117,6 +122,6 @@ export function authzMiddleware(authz: Authz): RequestHandler { req.apiDoc.components.schemas.TeamSelfService as TeamSelfService as PermissionSchema, otomi, ) - return authorize(req, res, next, authz, otomi.db) + return authorize(req, res, next, authz, otomi.repoService) } } diff --git a/src/middleware/jwt.ts b/src/middleware/jwt.ts index 75de37531..1c142a9bc 100644 --- a/src/middleware/jwt.ts +++ b/src/middleware/jwt.ts @@ -37,7 +37,7 @@ export function getUser(user: JWT, otomi: OtomiStack): SessionUser { const teamId = group.substring(5) if (group.substring(0, 5) === 'team-' && !sessionUser.teams.includes(teamId)) { // we might be assigned team-* without that team yet existing in the values, so ignore those - const existing = otomi.db.getItemReference('teams', { id: teamId }, false) + const existing = otomi.repoService.getTeamConfigService(teamId).getSettings() if (existing) sessionUser.teams.push(teamId) } }) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index 6b921df13..d25f966e8 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -10,7 +10,7 @@ import { Server } from 'socket.io' import { ApiNotReadyError } from 'src/error' import { OpenApiRequestExt } from 'src/otomi-models' import { default as OtomiStack, rootPath } from 'src/otomi-stack' -import { EDITOR_INACTIVITY_TIMEOUT, cleanEnv } from 'src/validators' +import { cleanEnv, EDITOR_INACTIVITY_TIMEOUT } from 'src/validators' import { v4 as uuidv4 } from 'uuid' const debug = Debug('otomi:session') @@ -41,10 +41,11 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis if (env.isTest) return readOnlyStack if (!sessions[sessionId]) { debug(`Creating session ${sessionId} for user ${editor}`) - sessions[sessionId] = new OtomiStack(editor, sessionId, readOnlyStack.db) + sessions[sessionId] = new OtomiStack(editor, sessionId) + await sessions[sessionId].initRepo(readOnlyStack.repoService) // init repo without inflating db from files as its slow and we just need a copy of the db - await sessions[sessionId].initRepo(true) - sessions[sessionId].db = cloneDeep(readOnlyStack.db) + await sessions[sessionId].initGit(true) + sessions[sessionId].repoService.setRepo(cloneDeep(readOnlyStack.repoService.getRepo())) } else sessions[sessionId].sessionId = sessionId return sessions[sessionId] } diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 407edfe20..244d9366a 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -37,6 +37,8 @@ export type TeamAuthz = components['schemas']['TeamAuthz'] export type Alerts = Settings['alerts'] export type Cluster = Settings['cluster'] export type Dns = Settings['dns'] +export type Ingress = Settings['ingress'] +export type Smtp = Settings['smtp'] export type Kms = Settings['kms'] export type Oidc = Settings['oidc'] export type Otomi = Settings['otomi'] @@ -145,3 +147,35 @@ export interface Core { teamConfig: Record version: number } + +export interface Repo { + apps: Record + alerts: Alerts + cluster: Cluster + databases: Record + dns: Dns + ingress: Ingress + kms: Kms + obj: Record + oidc: Oidc + otomi: Otomi + platformBackups: Record + smtp: Smtp + users: User[] + versions: Record + teamConfig: Record +} + +export interface TeamConfig { + apps: Record + backups: Backup[] + builds: Build[] + netpols: Netpol[] + policies: Policies + projects: Project[] + sealedSecrets: SealedSecret[] + services: Service[] + settings: Team + workloads: Workload[] + workloadValues: WorkloadValues[] +} diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index a7dcf6103..df5469da5 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -3,15 +3,14 @@ import * as k8s from '@kubernetes/client-node' import { V1ObjectReference } from '@kubernetes/client-node' import Debug from 'debug' -import { ObjectStorageKeyRegions, getRegions } from '@linode/api-v4' +import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' import { emptyDir, pathExists, unlink } from 'fs-extra' -import { readFile, readdir, writeFile } from 'fs/promises' +import { readdir, readFile, writeFile } from 'fs/promises' import { generate as generatePassword } from 'generate-password' import { cloneDeep, filter, get, isArray, isEmpty, map, omit, pick, set, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' -import Db from 'src/db' import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' -import { DbMessage, cleanAllSessions, cleanSession, getIo, getSessionStack } from 'src/middleware' +import { cleanAllSessions, cleanSession, DbMessage, getIo, getSessionStack } from 'src/middleware' import { App, Backup, @@ -25,8 +24,8 @@ import { Policies, Policy, Project, + Repo, SealedSecret, - Secret, Service, Session, SessionUser, @@ -39,9 +38,10 @@ import { Workload, WorkloadValues, } from 'src/otomi-models' -import getRepo, { Repo } from 'src/repo' +import getRepo, { Git } from 'src/git' import { arrayToObject, getServiceUrl, getValuesSchema, objectToArray, removeBlankAttributes } from 'src/utils' import { + cleanEnv, CUSTOM_ROOT_CA, DEFAULT_PLATFORM_ADMIN_EMAIL, EDITOR_INACTIVITY_TIMEOUT, @@ -56,7 +56,6 @@ import { PREINSTALLED_EXCLUDED_APPS, TOOLS_HOST, VERSIONS, - cleanEnv, } from 'src/validators' import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' import { @@ -83,6 +82,8 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' +import { loadValues } from './repo' +import { RepoService } from './services/RepoService' interface ExcludedApp extends App { managed: boolean @@ -191,17 +192,15 @@ export const rootPath = '/tmp/otomi/values' export default class OtomiStack { private coreValues: Core - - db: Db editor?: string sessionId?: string isLoaded = false - repo: Repo + git: Git + repoService: RepoService - constructor(editor?: string, sessionId?: string, inDb?: Db) { + constructor(editor?: string, sessionId?: string) { this.editor = editor this.sessionId = sessionId ?? 'main' - this.db = inDb ?? new Db() } getAppList() { @@ -214,12 +213,11 @@ export default class OtomiStack { } async getValues(query): Promise> { - return (await this.repo.requestValues(query)).data + return (await this.git.requestValues(query)).data } getRepoPath() { if (env.isTest || this.sessionId === undefined) return env.GIT_LOCAL_PATH - const folder = `${rootPath}/${this.sessionId}` - return folder + return `${rootPath}/${this.sessionId}` } async init(): Promise { @@ -234,17 +232,28 @@ export default class OtomiStack { } } - async initRepo(skipDbInflation = false): Promise { + async initRepo(repoService?: RepoService): Promise { + if (repoService) { + this.repoService = repoService + return + } else { + const repo = (await loadValues(this.getRepoPath())) as Repo + this.repoService = new RepoService(repo) + } + } + + async initGit(skipDbInflation = false): Promise { await this.init() + await this.initRepo() // every editor gets their own folder to detect conflicts upon deploy const path = this.getRepoPath() const branch = env.GIT_BRANCH const url = env.GIT_REPO_URL for (;;) { try { - this.repo = await getRepo(path, url, env.GIT_USER, env.GIT_EMAIL, env.GIT_PASSWORD, branch) - await this.repo.pull() - if (await this.repo.fileExists('env/cluster.yaml')) break + this.git = await getRepo(path, url, env.GIT_USER, env.GIT_EMAIL, env.GIT_PASSWORD, branch) + await this.git.pull() + if (await this.git.fileExists('env/cluster.yaml')) break debug(`Values are not present at ${url}:${branch}`) } catch (e) { debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) @@ -278,24 +287,14 @@ export default class OtomiStack { } getSettingsInfo(): SettingsInfo { - const settings = this.db.db.get(['settings']).value() as Settings - const { cluster, dns, obj, otomi, ingress, smtp } = pick(settings, [ - 'cluster', - 'dns', - 'obj', - 'otomi', - 'ingress', - 'smtp', - ]) as Settings - const settingsInfo = { - cluster: pick(cluster, ['name', 'domainSuffix', 'provider']), - dns: pick(dns, ['zones']), - obj: pick(obj, ['provider']), - otomi: pick(otomi, ['hasExternalDNS', 'hasExternalIDP', 'isPreInstalled']), - smtp: pick(smtp, ['smarthost']), - ingressClassNames: map(ingress?.classes, 'className') ?? [], + return { + cluster: pick(this.repoService.getCluster(), ['name', 'domainSuffix', 'provider']), + dns: pick(this.repoService.getDns(), ['zones']), + obj: pick(this.repoService.getObj(), ['provider']), + otomi: pick(this.repoService.getOtomi(), ['hasExternalDNS', 'hasExternalIDP', 'isPreInstalled']), + smtp: pick(this.repoService.getSmtp(), ['smarthost']), + ingressClassNames: map(this.repoService.getIngress()?.classes, 'className') ?? [], } as SettingsInfo - return settingsInfo } async createObjWizard(data: ObjWizard): Promise { @@ -375,7 +374,7 @@ export default class OtomiStack { } getSettings(keys?: string[]): Settings { - const settings = this.db.db.get(['settings']).value() + const settings = this.repoService.getSettings() if (!keys) return settings return pick(settings, keys) as Settings } @@ -383,10 +382,10 @@ export default class OtomiStack { async loadIngressApps(id: string): Promise { try { debug(`Loading ingress apps for ${id}`) - const content = await this.repo.loadConfig('env/apps/ingress-nginx.yaml', 'env/apps/secrets.ingress-nginx.yaml') + const content = await this.git.loadConfig('env/apps/ingress-nginx.yaml', 'env/apps/secrets.ingress-nginx.yaml') const values = content?.apps?.['ingress-nginx'] ?? {} const teamId = 'admin' - this.db.createItem('apps', { enabled: true, values, rawValues: {}, teamId }, { teamId, id }, id) + this.repoService.getTeamConfigService(teamId).createApp(id, { enabled: true, values, rawValues: {}, teamId }) debug(`Ingress app loaded for ${id}`) } catch (error) { debug(`Failed to load ingress apps for ${id}:`) @@ -398,9 +397,10 @@ export default class OtomiStack { debug(`Removing ingress apps for ${id}`) const path = `env/apps/${id}.yaml` const secretsPath = `env/apps/secrets.${id}.yaml` - this.db.deleteItem('apps', { teamId: 'admin', id }) - await this.repo.removeFile(path) - await this.repo.removeFile(secretsPath) + const teamId = 'admin' + this.repoService.getTeamConfigService(teamId).deleteApp(id) + await this.git.removeFile(path) + await this.git.removeFile(secretsPath) debug(`Ingress app removed for ${id}`) } catch (error) { debug(`Failed to remove ingress app for ${id}:`) @@ -430,7 +430,7 @@ export default class OtomiStack { } async editSettings(data: Settings, settingId: string): Promise { - const settings = this.db.db.get('settings').value() as Settings + const settings = this.repoService.getSettings() await this.editIngressApps(settings, data, settingId) const updatedSettingsData: any = { ...data } // Preserve the otomi.adminPassword when editing otomi settings @@ -441,20 +441,24 @@ export default class OtomiStack { } } settings[settingId] = removeBlankAttributes(updatedSettingsData[settingId] as Record) - this.db.db.set('settings', settings).write() + this.repoService.updateSettings(settings) const secretPaths = this.getSecretPaths() await this.saveSettings(secretPaths) - await this.doDeployment(['settings']) + await this.doDeployment([ + 'alerts', + 'cluster', + 'dns', + 'ingress', + 'kms', + 'obj', + 'oidc', + 'otomi', + 'platformBackups', + 'smtp', + ]) return settings } - // Check if the collection name already exists in any collection - isCollectionNameTaken(collectionName: string, teamId: string, name: string): boolean { - return this.db.getCollection(collectionName).some((e: any) => { - return e.teamId === teamId && e.name === name - }) - } - filterExcludedApp(apps: App | App[]) { const excludedApps = PREINSTALLED_EXCLUDED_APPS.default.apps const settingsInfo = this.getSettingsInfo() @@ -473,30 +477,29 @@ export default class OtomiStack { } getApp(teamId: string, id: string): App | ExcludedApp { - // @ts-ignore - const app = this.db.getItem('apps', { teamId, id }) as App + const app = this.repoService.getTeamConfigService(teamId).getApp(id) as App this.filterExcludedApp(app) if (teamId === 'admin') return app - const adminApp = this.db.getItem('apps', { teamId: 'admin', id: app.id }) as App + const adminApp = this.repoService.getTeamConfigService(teamId).getApp(id) as App return { ...cloneDeep(app), enabled: adminApp.enabled } } getApps(teamId: string, picks?: string[]): Array { - const apps = this.db.getCollection('apps', { teamId }) as Array + const apps = this.repoService.getTeamConfigService(teamId).getApps() as Array const providerSpecificApps = this.filterExcludedApp(apps) as App[] if (teamId === 'admin') return providerSpecificApps let teamApps = providerSpecificApps.map((app: App) => { - const adminApp = this.db.getItem('apps', { teamId: 'admin', id: app.id }) as App + const adminApp = this.repoService.getTeamConfigService('admin').getApp(app.id) as App return { ...cloneDeep(app), enabled: adminApp.enabled } }) if (!picks) return teamApps if (picks.includes('enabled')) { - const adminApps = this.db.getCollection('apps', { teamId: 'admin' }) as Array + const adminApps = this.repoService.getTeamConfigService('admin').getApps() as Array teamApps = adminApps.map((adminApp) => { const teamApp = teamApps.find((app) => app.id === adminApp.id) @@ -507,17 +510,16 @@ export default class OtomiStack { return teamApps.map((app) => pick(app, picks)) as Array } - async editApp(teamId, id, data: App): Promise { - // @ts-ignore - let app: App = this.db.getItem('apps', { teamId, id }) + async editApp(teamId: string, id: string, data: App): Promise { + let app: App = this.repoService.getTeamConfigService(teamId).getApp(id) // Shallow merge, so only first level attributes can be replaced (values, rawValues, etc.) app = { ...app, ...data } - const updatedApp = this.db.updateItem('apps', app as Record, { teamId, id }) as App + this.repoService.getTeamConfigService(teamId).updateApp(id, app) const secretPaths = this.getSecretPaths() // also save admin apps await this.saveAdminApps(secretPaths) await this.doDeployment(['apps']) - return updatedApp + return this.repoService.getTeamConfigService(teamId).getApp(id) as App } canToggleApp(id: string): boolean { @@ -529,8 +531,8 @@ export default class OtomiStack { ids.map((id) => { // we might be given a dep that is only relevant to core, or // which is essential, so skip it - const orig = this.db.getItemReference('apps', { teamId, id }, false) as App - if (orig && this.canToggleApp(id)) this.db.updateItem('apps', { enabled }, { teamId, id }, true) + const orig = this.repoService.getTeamConfigService(teamId).getApp(id) as App + if (orig && this.canToggleApp(id)) this.repoService.getTeamConfigService(teamId).updateApp(id, { enabled }) }) const secretPaths = this.getSecretPaths() // also save admin apps @@ -543,12 +545,12 @@ export default class OtomiStack { const appId = isIngressApp ? 'ingress-nginx' : appInstanceId const path = `env/apps/${appInstanceId}.yaml` const secretsPath = `env/apps/secrets.${appInstanceId}.yaml` - const content = await this.repo.loadConfig(path, secretsPath) + const content = await this.git.loadConfig(path, secretsPath) let values = content?.apps?.[appInstanceId] ?? {} if (appInstanceId === 'ingress-nginx-platform') { - const isIngressNginxPlatformAppExists = await this.repo.fileExists(`env/apps/ingress-nginx-platform.yaml`) + const isIngressNginxPlatformAppExists = await this.git.fileExists(`env/apps/ingress-nginx-platform.yaml`) if (!isIngressNginxPlatformAppExists) { - const defaultIngressNginxContent = await this.repo.loadConfig( + const defaultIngressNginxContent = await this.git.loadConfig( `env/apps/ingress-nginx.yaml`, `env/apps/secrets.ingress-nginx.yaml`, ) @@ -564,7 +566,7 @@ export default class OtomiStack { // we do not want to send enabled flag to the input forms delete values.enabled const teamId = 'admin' - this.db.createItem('apps', { enabled, values, rawValues, teamId }, { teamId, id: appInstanceId }, appInstanceId) + this.repoService.getTeamConfigService(teamId).createApp(appInstanceId, { enabled, values, rawValues, teamId }) } async loadApps(): Promise { @@ -577,7 +579,7 @@ export default class OtomiStack { } getTeams(): Array { - return this.db.getCollection('teams') as Array + return this.repoService.getAllTeamSettings() } getTeamSelfServiceFlags(id: string): TeamSelfService { @@ -590,7 +592,7 @@ export default class OtomiStack { } getTeam(id: string): Team { - return this.db.getItem('teams', { id }) as Team + return this.repoService.getTeamConfigService(id).getSettings() } async createTeam(data: Team, deploy = true): Promise { @@ -609,69 +611,64 @@ export default class OtomiStack { }) } - const team = this.db.createItem('teams', data, { id }, id) as Team + const teamConfig = this.repoService.createTeamConfig(id, data) + const team = teamConfig.settings const apps = getAppList() const core = this.getCore() apps.forEach((appId) => { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) // Admin apps are loaded by loadApps function - if (id !== 'admin' && (isShared || inTeamApps)) this.db.createItem('apps', {}, { teamId: id, id: appId }, appId) + if (id !== 'admin' && (isShared || inTeamApps)) this.repoService.getTeamConfigService(id).createApp(appId, {}) }) if (!data.id) { const policies = getPolicies() - this.db.db.set(`policies[${data.name}]`, policies).write() + this.repoService.getTeamConfigService(id).updatePolicies(policies) } if (deploy) { const secretPaths = this.getSecretPaths() await this.saveTeams(secretPaths) - await this.doDeployment(['teams']) + await this.doDeployment(['settings'], id) } return team } async editTeam(id: string, data: Team): Promise { - const team = this.db.updateItem('teams', data, { id }) as Team + const team = this.repoService.getTeamConfigService(id).updateSettings(data) const secretPaths = this.getSecretPaths() await this.saveTeams(secretPaths) - await this.doDeployment(['teams']) + await this.doDeployment(['settings'], id) return team } async deleteTeam(id: string): Promise { - try { - this.db.deleteItem('services', { id }) - } catch (e) { - // no services found - } - this.db.deleteItem('teams', { id }) + this.repoService.deleteTeamConfig(id) const secretPaths = this.getSecretPaths() await this.saveTeams(secretPaths) - await this.doDeployment(['teams']) + await this.doDeployment(['teamConfig']) } getTeamServices(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('services', ids) as Array + return this.repoService.getTeamConfigService(teamId).getServices() } getTeamBackups(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('backups', ids) as Array + return this.repoService.getTeamConfigService(teamId).getBackups() } getAllBackups(): Array { - return this.db.getCollection('backups') as Array + return this.repoService.getPlatformBackups() } async createBackup(teamId: string, data: Backup): Promise { validateBackupFields(data.name, data.ttl) try { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const backup = this.db.createItem('backups', { ...data, teamId }, { teamId, name: data.name }) as Backup + const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) + await this.saveTeamBackups(teamId) - await this.doDeployment(['backups']) + await this.doDeployment(['backups'], teamId) return backup } catch (err) { if (err.code === 409) err.publicMessage = 'Backup name already exists' @@ -679,40 +676,38 @@ export default class OtomiStack { } } - getBackup(id: string): Backup { - return this.db.getItem('backups', { id }) as Backup + getBackup(teamId: string, id: string): Backup { + return this.repoService.getTeamConfigService(teamId).getBackup(id) } async editBackup(id: string, data: Backup): Promise { validateBackupFields(data.name, data.ttl) - const backup = this.db.updateItem('backups', data, { id }) as Backup + const backup = this.repoService.getTeamConfigService(data.teamId!).updateBackup(id, data) await this.saveTeamBackups(data.teamId!) - await this.doDeployment(['backups']) + await this.doDeployment(['backups'], data.teamId) return backup } - async deleteBackup(id: string): Promise { - const backup = this.getBackup(id) - this.db.deleteItem('backups', { id }) - await this.saveTeamBackups(backup.teamId!) - await this.doDeployment(['backups']) + async deleteBackup(teamId: string, id: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteBackup(id) + await this.saveTeamBackups(teamId) + await this.doDeployment(['backups'], teamId) } getTeamNetpols(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('netpols', ids) as Array + return this.repoService.getTeamConfigService(teamId).getNetpols() } getAllNetpols(): Array { - return this.db.getCollection('netpols') as Array + return this.repoService.getAllNetpols() } async createNetpol(teamId: string, data: Netpol): Promise { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const netpol = this.db.createItem('netpols', { ...data, teamId }, { teamId, name: data.name }) as Netpol + const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId) - await this.doDeployment(['netpols']) + await this.doDeployment(['netpols'], teamId) return netpol } catch (err) { if (err.code === 409) err.publicMessage = 'Network policy name already exists' @@ -720,27 +715,26 @@ export default class OtomiStack { } } - getNetpol(id: string): Netpol { - return this.db.getItem('netpols', { id }) as Netpol + getNetpol(teamId: string, id: string): Netpol { + return this.repoService.getTeamConfigService(teamId).getNetpol(id) } async editNetpol(id: string, data: Netpol): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const netpol = this.db.updateItem('netpols', data, { id }) as Netpol + const netpol = this.repoService.getTeamConfigService(data.teamId!).updateNetpol(id, data) await this.saveTeamNetpols(netpol.teamId!) - await this.doDeployment(['netpols']) + await this.doDeployment(['netpols'], data.teamId) return netpol } - async deleteNetpol(id: string): Promise { - const netpol = this.getNetpol(id) - this.db.deleteItem('netpols', { id }) - await this.saveTeamNetpols(netpol.teamId!) - await this.doDeployment(['netpols']) + async deleteNetpol(teamId: string, id: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteNetpol(id) + await this.saveTeamNetpols(teamId) + await this.doDeployment(['netpols'], teamId) } getAllUsers(sessionUser: SessionUser): Array { - const users = this.db.getCollection('users') as Array + const users = this.repoService.getUsers() if (sessionUser.isPlatformAdmin) return users else if (sessionUser.isTeamAdmin) { const usersWithBasicInfo = users.map((user) => { @@ -766,7 +760,7 @@ export default class OtomiStack { strict: true, }) const user = { ...data, initialPassword } - let existingUsers = this.db.getCollection('users') as any + let existingUsersEmail = this.repoService.getUsersEmail() if (!env.isDev) { const { otomi, cluster } = this.getSettings(['otomi', 'cluster']) const keycloak = this.getApp('admin', 'keycloak') @@ -774,13 +768,13 @@ export default class OtomiStack { const realm = 'otomi' const username = keycloak?.values?.adminUsername as string const password = otomi?.adminPassword as string - existingUsers = await getKeycloakUsers(keycloakBaseUrl, realm, username, password) + existingUsersEmail = await getKeycloakUsers(keycloakBaseUrl, realm, username, password) } try { - if (existingUsers.some((existingUser) => existingUser.email === user.email)) + if (existingUsersEmail.some((existingUser) => existingUser === user.email)) throw new AlreadyExists('User email already exists') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const createdUser = this.db.createItem('users', user, { name: user.email }) as User + const createdUser = this.repoService.createUser(user) await this.saveUsers() await this.doDeployment(['users']) return createdUser @@ -791,26 +785,26 @@ export default class OtomiStack { } getUser(id: string): User { - return this.db.getItem('users', { id }) as User + return this.repoService.getUser(id) } async editUser(id: string, data: User): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const user = this.db.updateItem('users', data, { id }) as User + const user = this.repoService.updateUser(id, data) await this.saveUsers() await this.doDeployment(['users']) return user } async deleteUser(id: string): Promise { - const user = this.db.getItem('users', { id }) as User + const user = this.repoService.getUser(id) if (user.email === env.DEFAULT_PLATFORM_ADMIN_EMAIL) { const error = new OtomiError('Forbidden') error.code = 403 error.publicMessage = 'Cannot delete the default platform admin user' throw error } - this.db.deleteItem('users', { id }) + this.repoService.deleteUser(id) await this.saveUsers() await this.doDeployment(['users']) } @@ -819,41 +813,38 @@ export default class OtomiStack { data: Pick[], ): Promise> { data.forEach((user) => { - const existingUser = this.db.getItem('users', { id: user.id }) as User - this.db.updateItem('users', { ...existingUser, teams: user.teams }, { id: user.id }) as User + const existingUser = this.repoService.getUser(user.id!) + this.repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) }) - const users = this.db.getCollection('users') as Array + const users = this.repoService.getUsers() await this.saveUsers() await this.doDeployment(['users']) return users } getTeamProjects(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('projects', ids) as Array + return this.repoService.getTeamConfigService(teamId).getProjects() } getAllProjects(): Array { - return this.db.getCollection('projects') as Array + return this.repoService.getAllProjects() } // Creates a new project and reserves a given name for 'builds', 'workloads' and 'services' resources async createProject(teamId: string, data: Project): Promise { // Check if the project name already exists in any collection - const projectNameTaken = ['builds', 'workloads', 'services'].some((collectionName) => - this.isCollectionNameTaken(collectionName, teamId, data.name), - ) + const projectNameTaken = this.repoService.getTeamConfigService(teamId).doesProjectNameExist(data.name) const projectNameTakenPublicMessage = `In the team '${teamId}' there is already a resource that match the project name '${data.name}'` try { if (projectNameTaken) throw new AlreadyExists(projectNameTakenPublicMessage) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const project = this.db.createItem('projects', { ...data, teamId }, { teamId, name: data.name }) as Project + const project = this.repoService.getTeamConfigService(teamId).createProject({ ...data, teamId }) await this.saveTeamProjects(teamId) await this.saveTeamBuilds(teamId) await this.saveTeamWorkloads(teamId) await this.saveTeamServices(teamId) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services']) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -862,49 +853,69 @@ export default class OtomiStack { } } - getProject(id: string): Project { - const p = this.db.getItem('projects', { id }) as Project - let b, w, wv, s + getProject(teamId: string, id: string): Project { + const project = this.repoService.getTeamConfigService(teamId).getProject(id) + let build, workload, workloadValues, services try { - b = this.db.getItem('builds', { id: p.build?.id }) as Build + build = this.repoService.getTeamConfigService(teamId).getBuild(project.build!.id!) } catch (err) { - b = {} + build = {} } try { - w = this.db.getItem('workloads', { id: p.workload?.id }) as Workload + workload = this.repoService.getTeamConfigService(teamId).getWorkload(project.workload!.id!) } catch (err) { - w = {} + workload = {} } try { - wv = this.db.getItem('workloadValues', { id: p.workloadValues?.id }) as WorkloadValues + workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(project.workloadValues!.id!) } catch (err) { - wv = {} + workloadValues = {} } try { - s = this.db.getItem('services', { id: p.service?.id }) as Service + services = this.repoService.getTeamConfigService(teamId).getService(project.service!.id!) } catch (err) { - s = {} + services = {} + } + return { + id, + teamId, + ...project, + name: project.name, + build, + workload, + workloadValues, + service: services, } - return { ...p, build: b, workload: w, workloadValues: wv, service: s } } - async editProject(id: string, data: Project): Promise { - const { build, workload, workloadValues, service, teamId, name } = data + async editProject(teamId: string, id: string, data: Project): Promise { + const { build, workload, workloadValues, service, name } = data const { values } = workloadValues as WorkloadValues let b, w, wv, s - if (!build?.id && build?.mode) b = this.db.createItem('builds', { ...build, teamId }, { teamId, name }) as Build - else if (build?.id) b = this.db.updateItem('builds', build, { id: build.id }) as Build + if (!build?.id && build?.mode) { + b = this.repoService.getTeamConfigService(teamId).createBuild({ ...build, teamId }) + } else if (build?.id) { + b = this.repoService.getTeamConfigService(teamId).updateBuild(build.id, build) + } - if (!workload?.id) w = this.db.createItem('workloads', { ...workload, teamId }, { teamId, name }) as Workload - else w = this.db.updateItem('workloads', workload, { id: workload.id }) as Workload + if (workload && !workload?.id) { + w = this.repoService.getTeamConfigService(teamId).createWorkload(workload) + } else if (workload?.id) { + w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.id, workload) + } - if (!data.workloadValues?.id) - wv = this.db.createItem('workloadValues', { teamId, values }, { teamId, name }, w.id) as WorkloadValues - else wv = this.db.updateItem('workloadValues', { teamId, values }, { id: workloadValues?.id }) as WorkloadValues + if (!data.workloadValues?.id) { + wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ teamId, values }) + } else if (workloadValues?.id) { + wv = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(workloadValues.id, { teamId, values }) + } - if (!service?.id) s = this.db.createItem('services', { ...service, teamId }, { teamId, name }) as Service - else s = this.db.updateItem('services', service, { id: service.id }) as Service + if (service && !service?.id) { + s = this.repoService.getTeamConfigService(teamId).createService({ ...service, teamId }) + } else if (service?.id) { + s = this.repoService.getTeamConfigService(teamId).updateService(service.id, service) + } const updatedData = { id, @@ -917,28 +928,28 @@ export default class OtomiStack { } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const project = this.db.updateItem('projects', updatedData, { id }) as Project - await this.saveTeamProjects(project.teamId!) - await this.saveTeamBuilds(project.teamId!) - await this.saveTeamWorkloads(project.teamId!) - await this.saveTeamServices(project.teamId!) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services']) + const project = this.repoService.getTeamConfigService(teamId).updateProject(id, updatedData) + await this.saveTeamProjects(teamId) + await this.saveTeamBuilds(teamId) + await this.saveTeamWorkloads(teamId) + await this.saveTeamServices(teamId) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) return project } // Deletes a project and all its related resources - async deleteProject(id: string): Promise { - const p = this.db.getItem('projects', { id }) as Project - if (p.build?.id) this.db.deleteItem('builds', { id: p.build.id }) - if (p.workload?.id) this.db.deleteItem('workloads', { id: p.workload.id }) - if (p.workloadValues?.id) this.db.deleteItem('workloadValues', { id: p.workloadValues.id }) - if (p.service?.id) this.db.deleteItem('services', { id: p.service.id }) - this.db.deleteItem('projects', { id }) - await this.saveTeamProjects(p.teamId!) - await this.saveTeamBuilds(p.teamId!) - await this.saveTeamWorkloads(p.teamId!) - await this.saveTeamServices(p.teamId!) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services']) + async deleteProject(teamId: string, id: string): Promise { + const p = this.repoService.getTeamConfigService(teamId).getProject(id) + if (p.build?.id) this.repoService.getTeamConfigService(teamId).deleteBuild(p.build.id) + if (p.workload?.id) this.repoService.getTeamConfigService(teamId).deleteWorkload(p.workload.id) + if (p.workloadValues?.id) this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(p.workloadValues.id) + if (p.service?.id) this.repoService.getTeamConfigService(teamId).deleteService(p.service.id) + this.repoService.getTeamConfigService(teamId).deleteProject(id) + await this.saveTeamProjects(teamId) + await this.saveTeamBuilds(teamId) + await this.saveTeamWorkloads(teamId) + await this.saveTeamServices(teamId) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } getTeamCoderepos(teamId: string): Array { @@ -1028,15 +1039,14 @@ export default class OtomiStack { } getDashboard(teamId: string): Array { - const ids = teamId !== 'admin' ? { teamId } : undefined - const projects = this.db.getCollection('projects', ids) as Array - const builds = this.db.getCollection('builds', ids) as Array - const workloads = this.db.getCollection('workloads', ids) as Array - const services = this.db.getCollection('services', ids) as Array - const secrets = this.db.getCollection('sealed-secrets', ids) as Array - const netpols = this.db.getCollection('netpols', ids) as Array - - const inventory = [ + const projects = this.repoService.getTeamConfigService(teamId).getProjects() + const builds = this.repoService.getTeamConfigService(teamId).getBuilds() + const workloads = this.repoService.getTeamConfigService(teamId).getWorkloads() + const services = this.repoService.getTeamConfigService(teamId).getServices() + const secrets = this.repoService.getTeamConfigService(teamId).getSealedSecrets() + const netpols = this.repoService.getTeamConfigService(teamId).getNetpols() + + return [ { name: 'projects', count: projects?.length }, { name: 'builds', count: builds?.length }, { name: 'workloads', count: workloads?.length }, @@ -1044,24 +1054,22 @@ export default class OtomiStack { { name: 'sealed secrets', count: secrets?.length }, { name: 'network policies', count: netpols?.length }, ] - return inventory } getTeamBuilds(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('builds', ids) as Array + return this.repoService.getTeamConfigService(teamId).getBuilds() } getAllBuilds(): Array { - return this.db.getCollection('builds') as Array + return this.repoService.getAllBuilds() } async createBuild(teamId: string, data: Build): Promise { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const build = this.db.createItem('builds', { ...data, teamId }, { teamId, name: data.name }) as Build + const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuilds(teamId) - await this.doDeployment(['builds']) + await this.doDeployment(['builds'], teamId) return build } catch (err) { if (err.code === 409) err.publicMessage = 'Build name already exists' @@ -1069,44 +1077,41 @@ export default class OtomiStack { } } - getBuild(id: string): Build { - return this.db.getItem('builds', { id }) as Build + getBuild(teamId: string, id: string): Build { + return this.repoService.getTeamConfigService(teamId).getBuild(id) } async editBuild(id: string, data: Build): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const build = this.db.updateItem('builds', data, { id }) as Build + const build = this.repoService.getTeamConfigService(data.teamId!).updateBuild(id, data) await this.saveTeamBuilds(build.teamId!) - await this.doDeployment(['builds']) + await this.doDeployment(['builds'], data.teamId) return build } - async deleteBuild(id: string): Promise { - const build = this.getBuild(id) - const p = this.db.getCollection('projects') as Array + async deleteBuild(teamId: string, id: string): Promise { + const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { if (project?.build?.id === id) { - const updatedData = { ...project, build: null } - this.db.updateItem('projects', updatedData, { id: project.id }) as Project + const updatedData = { ...project, build: undefined } + this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) - this.db.deleteItem('builds', { id }) - await this.saveTeamBuilds(build.teamId!) - await this.doDeployment(['builds']) + this.repoService.getTeamConfigService(teamId).deleteBuild(id) + await this.saveTeamBuilds(teamId) + await this.doDeployment(['builds'], teamId) } getTeamPolicies(teamId: string): Policies { - const policies = this.db.db.get(['policies']).value() - return policies[teamId] + return this.repoService.getTeamConfigService(teamId).getPolicies() } getAllPolicies(): Record { - return this.db.db.get(['policies']).value() + return this.repoService.getAllPolicies() } getPolicy(teamId: string, id: string): Policy { - const policies = this.db.db.get(['policies']).value() - return policies[teamId][id] + return this.repoService.getTeamConfigService(teamId).getPolicy(id) } async editPolicy(teamId: string, policyId: string, data: Policy): Promise { @@ -1114,7 +1119,7 @@ export default class OtomiStack { teamPolicies[policyId] = removeBlankAttributes(data) const policy = this.getPolicy(teamId, policyId) await this.saveTeamPolicies(teamId) - await this.doDeployment(['policies']) + await this.doDeployment(['policies'], teamId) return policy } @@ -1204,12 +1209,11 @@ export default class OtomiStack { } getTeamWorkloads(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('workloads', ids) as Array + return this.repoService.getTeamConfigService(teamId).getWorkloads() } getAllWorkloads(): Array { - return this.db.getCollection('workloads') as Array + return this.repoService.getAllWorkloads() } async getWorkloadCatalog(data: { url: string; sub: string; teamId: string }): Promise { @@ -1230,15 +1234,10 @@ export default class OtomiStack { async createWorkload(teamId: string, data: Workload): Promise { try { - const workload = this.db.createItem('workloads', { ...data, teamId }, { teamId, name: data.name }) as Workload - this.db.createItem( - 'workloadValues', - { teamId, values: {} }, - { teamId, name: workload.name }, - workload.id, - ) as WorkloadValues + const workload = this.repoService.getTeamConfigService(teamId).createWorkload({ ...data, teamId }) + this.repoService.getTeamConfigService(teamId).createWorkloadValues({ teamId, values: {}, id: workload.id }) await this.saveTeamWorkloads(teamId) - await this.doDeployment(['workloads', 'workloadValues']) + await this.doDeployment(['workloads', 'workloadValues'], teamId) return workload } catch (err) { if (err.code === 409) err.publicMessage = 'Workload name already exists' @@ -1246,60 +1245,55 @@ export default class OtomiStack { } } - getWorkload(id: string): Workload { - return this.db.getItem('workloads', { id }) as Workload + getWorkload(teamId: string, id: string): Workload { + return this.repoService.getTeamConfigService(teamId).getWorkload(id) } async editWorkload(id: string, data: Workload): Promise { - const workload = this.db.updateItem('workloads', data, { id }) as Workload + const workload = this.repoService.getTeamConfigService(data.teamId!).updateWorkload(id, data) await this.saveTeamWorkloads(workload.teamId!) - await this.doDeployment(['workloads', 'workloadValues']) + await this.doDeployment(['workloads', 'workloadValues'], data.teamId) return workload } - async deleteWorkload(id: string): Promise { - const p = this.db.getCollection('projects') as Array + async deleteWorkload(teamId: string, id: string): Promise { + const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { if (project?.workload?.id === id) { - const updatedData = { ...project, workload: null, workloadValues: null } - this.db.updateItem('projects', updatedData, { id: project.id }) as Project + const updatedData = { ...project, workload: undefined, workloadValues: undefined } + this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) - const workloadValues = this.db.getItem('workloadValues', { id }) as WorkloadValues + const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) const path = getTeamWorkloadValuesFilePath(workloadValues.teamId!, workloadValues.name) - await this.repo.removeFile(path) - this.db.deleteItem('workloadValues', { id }) - this.db.deleteItem('workloads', { id }) + await this.git.removeFile(path) + this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(id) + this.repoService.getTeamConfigService(teamId).deleteWorkload(id) await this.saveTeamWorkloads(workloadValues.teamId!) - await this.doDeployment(['workloads', 'workloadValues']) + await this.doDeployment(['workloads', 'workloadValues'], teamId) } async editWorkloadValues(id: string, data: WorkloadValues): Promise { - const workloadValues = this.db.updateItem('workloadValues', data, { id }) as WorkloadValues + const workloadValues = this.repoService.getTeamConfigService(data.teamId!).updateWorkloadValues(id, data) await this.saveTeamWorkloads(workloadValues.teamId!) - await this.doDeployment(['workloadValues']) + await this.doDeployment(['workloadValues'], workloadValues.teamId) return workloadValues } - getWorkloadValues(id: string): WorkloadValues { - return this.db.getItem('workloadValues', { id }) as WorkloadValues + getWorkloadValues(teamId: string, id: string): WorkloadValues { + return this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) } getAllServices(): Array { - return this.db.getCollection('services') as Array + return this.repoService.getAllServices() } async createService(teamId: string, data: Service): Promise { - this.checkPublicUrlInUse(data) + this.checkPublicUrlInUse(teamId, data) try { - const service = this.db.createItem( - 'services', - { ...data, teamId }, - { teamId, name: data.name }, - data?.id, - ) as Service + const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) await this.saveTeamServices(teamId) - await this.doDeployment(['services']) + await this.doDeployment(['services'], teamId) return service } catch (err) { if (err.code === 409) err.publicMessage = 'Service name already exists' @@ -1307,38 +1301,37 @@ export default class OtomiStack { } } - getService(id: string): Service { - return this.db.getItem('services', { id }) as Service + getService(teamId: string, id: string): Service { + return this.repoService.getTeamConfigService(teamId).getService(id) } async editService(id: string, data: Service): Promise { - const service = this.db.updateItem('services', data, { id }) as Service + const service = this.repoService.getTeamConfigService(data.teamId!).updateService(id, data) await this.saveTeamServices(service.teamId!) - await this.doDeployment(['services']) + await this.doDeployment(['services'], data.teamId) return service } - async deleteService(id: string, deleteProjectService = true): Promise { - const service = this.getService(id) + async deleteService(teamId: string, id: string, deleteProjectService = true): Promise { if (deleteProjectService) { - const p = this.db.getCollection('projects') as Array + const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { if (project?.service?.id === id) { - const updatedData = { ...project, service: null } - this.db.updateItem('projects', updatedData, { id: project.id }) as Project + const updatedData = { ...project, service: undefined } + this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) } - this.db.deleteItem('services', { id }) - await this.saveTeamServices(service.teamId!) - await this.doDeployment(['services']) + this.repoService.getTeamConfigService(teamId).deleteService(id) + await this.saveTeamServices(teamId) + await this.doDeployment(['services'], teamId) } - checkPublicUrlInUse(data: any): void { + checkPublicUrlInUse(teamId: string, data: any): void { // skip when editing or when svc is of type "cluster" as it has no url if (data.id || data?.ingress?.type === 'cluster') return const newSvc = data.ingress - const services = this.db.getCollection('services') + const services = this.repoService.getTeamConfigService(teamId).getServices() const servicesFiltered = filter(services, (svc: any) => { if (svc.ingress?.type !== 'cluster') { @@ -1385,25 +1378,37 @@ export default class OtomiStack { } } - async doDeployment(collectionIds?: string[]): Promise { + async doDeployment(collectionIds?: string[], teamId?: string): Promise { const rootStack = await getSessionStack() try { - // commit and pull-push remote root - await this.repo.save(this.editor!) - // update db with the new values + // Commit and push Git changes + await this.git.save(this.editor!) + if (collectionIds) { collectionIds.forEach((collectionId) => { - const collection = this.db.db.get(collectionId).value() - rootStack.db.db.set(collectionId, collection).write() + if (teamId && collectionId in this.repoService.getRepo().teamConfig[teamId]) { + // If a teamId is provided and collection is inside teamConfig, update the specific team + const collection = this.repoService.getTeamConfigService(teamId).getCollection(collectionId) + rootStack.repoService.getTeamConfigService(teamId).updateCollection(collectionId, collection) + } else { + // Otherwise, update the root repo collection + const collection = this.repoService.getCollection(collectionId) + if (collection) { + rootStack.repoService.updateCollection(collectionId, collection) + } + } }) } + debug(`Updated root stack values with ${this.sessionId} changes`) - // and remove editor from the session + + // Clean up the session await cleanSession(this.sessionId!) - const sha = await rootStack.repo.getCommitSha() + + // Emit pipeline status + const sha = await rootStack.git.getCommitSha() this.emitPipelineStatus(sha) } catch (e) { - // git conflict with upstream changes, clean up and restore the DB if (e instanceof GitPullError) await this.doRestore() const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } getIo().emit('db', msg) @@ -1416,7 +1421,7 @@ export default class OtomiStack { await emptyDir(rootPath) // and re-init root const rootStack = await getSessionStack() - await rootStack.initRepo() + await rootStack.initGit() } apiClient?: k8s.CoreV1Api @@ -1527,30 +1532,6 @@ export default class OtomiStack { return token } - createSecret(teamId: string, data: Record): Secret { - return this.db.createItem('secrets', { ...data, teamId }, { teamId, name: data.name }) as Secret - } - - editSecret(id: string, data: Secret): Secret { - return this.db.updateItem('secrets', data, { id }) as Secret - } - - deleteSecret(id: string): void { - this.db.deleteItem('secrets', { id }) - } - - getSecret(id: string): Secret { - return this.db.getItem('secrets', { id }) as Secret - } - - getAllSecrets(): Array { - return this.db.getCollection('secrets', {}) as Array - } - - getSecrets(teamId: string): Array { - return this.db.getCollection('secrets', { teamId }) as Array - } - async createSealedSecret(teamId: string, data: SealedSecret): Promise { const namespace = data.namespace ?? `team-${teamId}` const certificate = await getSealedSecretsCertificate() @@ -1565,14 +1546,12 @@ export default class OtomiStack { return { [obj.key]: encryptedItem } }) const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord - const sealedSecret = this.db.createItem( - 'sealedsecrets', - { ...data, teamId, encryptedData, namespace }, - { teamId, name: data.name }, - ) as SealedSecret + const sealedSecret = this.repoService + .getTeamConfigService(teamId) + .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.id!) - await this.doDeployment(['sealedsecrets']) + await this.doDeployment(['sealedsecrets'], teamId) return sealedSecret } catch (err) { if (err.code === 409) err.publicMessage = 'SealedSecret name already exists' @@ -1609,8 +1588,8 @@ export default class OtomiStack { await this.doDeployment(['sealedsecrets']) } - async getSealedSecret(id: string): Promise { - const sealedSecret = this.db.getItem('sealedsecrets', { id }) as SealedSecret + async getSealedSecret(teamId: string, id: string): Promise { + const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(id) const namespace = sealedSecret.namespace ?? `team-${sealedSecret.teamId}` const secretValues = (await getSecretValues(sealedSecret.name, namespace)) || {} const isDisabled = isEmpty(secretValues) @@ -1623,22 +1602,21 @@ export default class OtomiStack { } getAllSealedSecrets(): Array { - return this.db.getCollection('sealedsecrets', {}) as Array + return this.repoService.getAllSealedSecrets() } getSealedSecrets(teamId: string): Array { - return this.db.getCollection('sealedsecrets', { teamId }) as Array + return this.repoService.getTeamConfigService(teamId).getSealedSecrets() } async getSecretsFromK8s(teamId: string): Promise> { if (env.isDev) return [] - const secrets = await getTeamSecretsFromK8s(`team-${teamId}`) - return secrets + return await getTeamSecretsFromK8s(`team-${teamId}`) } async loadValues(): Promise>>>> { debug('Loading values') - await this.repo.initSops() + await this.git.initSops() await this.loadCluster() await this.loadSettings() await this.loadUsers() @@ -1648,31 +1626,33 @@ export default class OtomiStack { } async loadCluster(): Promise { - const data = await this.repo.loadConfig('env/cluster.yaml', 'env/secrets.cluster.yaml') - // @ts-ignore - this.db.db.get('settings').assign(data).write() + const { cluster } = await this.git.loadConfig('env/cluster.yaml', 'env/secrets.cluster.yaml') + this.repoService.updateCluster(cluster) } async loadSettings(): Promise { - const data: Record = await this.repo.loadConfig('env/settings.yaml', `env/secrets.settings.yaml`) - data.otomi!.nodeSelector = objectToArray((data.otomi!.nodeSelector ?? {}) as Record) - // @ts-ignore - this.db.db.get('settings').assign(data).write() + const data: Record = await this.git.loadConfig('env/settings.yaml', `env/secrets.settings.yaml`) + + if (data.otomi) { + data.otomi.nodeSelector = objectToArray((data.otomi.nodeSelector ?? {}) as Record) + } + + this.repoService.updateSettings(data) } async loadUsers(): Promise { - const { secretFilePostfix } = this.repo + const { secretFilePostfix } = this.git const relativePath = `env/secrets.users.yaml` const secretRelativePath = `${relativePath}${secretFilePostfix}` - if (!(await this.repo.fileExists(relativePath)) || !(await this.repo.fileExists(secretRelativePath))) { + if (!(await this.git.fileExists(relativePath)) || !(await this.git.fileExists(secretRelativePath))) { debug(`No users found`) return } - const data = await this.repo.readFile(secretRelativePath) + const data = await this.git.readFile(secretRelativePath) const inData: Array = get(data, `users`, []) inData.forEach((inUser) => { - const res: any = this.db.populateItem('users', { ...inUser }, undefined, inUser.id as string) - debug(`Loaded user: email: ${res.name}, id: ${res.id}`) + const user = this.repoService.createUser(inUser) + debug(`Loaded user: email: ${user.email}, id: ${user.id}`) }) } @@ -1708,43 +1688,43 @@ export default class OtomiStack { async loadTeamBackups(teamId: string): Promise { const relativePath = getTeamBackupsFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no backups yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const inData: Array = get(data, getTeamBackupsJsonPath(teamId), []) inData.forEach((inBackup) => { - const res: any = this.db.populateItem('backups', { ...inBackup, teamId }, undefined, inBackup.id as string) - debug(`Loaded backup: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) + const backup = this.repoService.getTeamConfigService(teamId).createBackup({ ...inBackup, teamId }) + debug(`Loaded backup: name: ${backup.name}, id: ${backup.id}, teamId: ${backup.teamId}`) }) } async loadTeamNetpols(teamId: string): Promise { const relativePath = getTeamNetpolsFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no network policies yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const inData: Array = get(data, getTeamNetpolsJsonPath(teamId), []) inData.forEach((inNetpol) => { - const res: any = this.db.populateItem('netpols', { ...inNetpol, teamId }, undefined, inNetpol.id as string) - debug(`Loaded network policy: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) + const netpol = this.repoService.getTeamConfigService(teamId).createNetpol({ ...inNetpol, teamId }) + debug(`Loaded network policy: name: ${netpol.name}, id: ${netpol.id}, teamId: ${netpol.teamId}`) }) } async loadTeamProjects(teamId: string): Promise { const relativePath = getTeamProjectsFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no projects yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const inData: Array = get(data, getTeamProjectsJsonPath(teamId), []) inData.forEach((inProject) => { - const res: any = this.db.populateItem('projects', { ...inProject, teamId }, undefined, inProject.id as string) - debug(`Loaded project: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) + const project = this.repoService.getTeamConfigService(teamId).createProject({ ...inProject, teamId }) + debug(`Loaded project: name: ${project.name}, id: ${project.id}, teamId: ${project.teamId}`) }) } @@ -1764,41 +1744,41 @@ export default class OtomiStack { async loadTeamBuilds(teamId: string): Promise { const relativePath = getTeamBuildsFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no builds yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const inData: Array = get(data, getTeamBuildsJsonPath(teamId), []) inData.forEach((inBuild) => { - const res: any = this.db.populateItem('builds', { ...inBuild, teamId }, undefined, inBuild.id as string) - debug(`Loaded build: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) + const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...inBuild, teamId }) + debug(`Loaded build: name: ${build.name}, id: ${build.id}, teamId: ${build.teamId}`) }) } async loadTeamPolicies(teamId: string): Promise { const relativePath = getTeamPoliciesFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no policies yet`) return } - const data = await this.repo.readFile(relativePath) - const inData: any = get(data, getTeamPoliciesJsonPath(teamId), {}) - this.db.db.set(`policies[${teamId}]`, inData).write() + const data = await this.git.readFile(relativePath) + const inData: Partial = get(data, getTeamPoliciesJsonPath(teamId), {}) + this.repoService.getTeamConfigService(teamId).updatePolicies(inData) debug(`Loaded policies of team: ${teamId}`) } async loadTeamWorkloads(teamId: string): Promise { const relativePath = getTeamWorkloadsFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no workloads yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const inData: Array = get(data, getTeamWorkloadsJsonPath(teamId), []) inData.forEach((inWorkload) => { - const res: any = this.db.populateItem('workloads', { ...inWorkload, teamId }, undefined, inWorkload.id as string) - debug(`Loaded workload: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) + const workload = this.repoService.getTeamConfigService(teamId).createWorkload({ ...inWorkload, teamId }) + debug(`Loaded workload: name: ${workload.name}, id: ${workload.id}, teamId: ${workload.teamId}`) }) const workloads = this.getTeamWorkloads(teamId) await Promise.all( @@ -1810,16 +1790,18 @@ export default class OtomiStack { async loadWorkloadValues(workload: Workload): Promise { const relativePath = getTeamWorkloadValuesFilePath(workload.teamId!, workload.name) - let data = { values: {} } as Record - if (!(await this.repo.fileExists(relativePath))) - debug(`The workload values file does not exists at ${relativePath}`) - else data = await this.repo.readFile(relativePath) + let data = { values: {} } as WorkloadValues + if (!(await this.git.fileExists(relativePath))) debug(`The workload values file does not exists at ${relativePath}`) + else { + const fileData = await this.git.readFile(relativePath) + data = { ...data, ...fileData } + } data.id = workload.id! data.teamId = workload.teamId! data.name = workload.name! try { - data.values = parseYaml(data.values as string) || {} + data.values = parseYaml(data.values as unknown as string) || {} } catch (error) { debug( `The values property does not seem to be a YAML formated string at ${relativePath}. Falling back to empty map.`, @@ -1827,13 +1809,13 @@ export default class OtomiStack { data.values = {} } - const res = this.db.populateItem('workloadValues', data, undefined, workload.id as string) as WorkloadValues + const res = this.repoService.getTeamConfigService(workload.teamId!).createWorkloadValues(data) debug(`Loaded workload values: name: ${res.name} id: ${res.id}, teamId: ${workload.teamId!}`) return res } async loadTeams(): Promise { - const mergedData: Core = await this.repo.loadConfig('env/teams.yaml', `env/secrets.teams.yaml`) + const mergedData: Core = await this.git.loadConfig('env/teams.yaml', `env/secrets.teams.yaml`) const tc = mergedData?.teamConfig || {} if (!tc.admin) tc.admin = { id: 'admin' } await Promise.all( @@ -1854,11 +1836,11 @@ export default class OtomiStack { async loadTeamServices(teamId: string): Promise { const relativePath = getTeamServicesFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { + if (!(await this.git.fileExists(relativePath))) { debug(`Team ${teamId} has no services yet`) return } - const data = await this.repo.readFile(relativePath) + const data = await this.git.readFile(relativePath) const services = get(data, getTeamServicesJsonPath(teamId), []) services.forEach((svc) => { this.loadService(svc, teamId) @@ -1866,7 +1848,7 @@ export default class OtomiStack { } async saveCluster(secretPaths?: string[]): Promise { - await this.repo.saveConfig( + await this.git.saveConfig( 'env/cluster.yaml', 'env/secrets.cluster.yaml', this.getSettings(['cluster']), @@ -1887,7 +1869,7 @@ export default class OtomiStack { if (this.canToggleApp(id)) apps[id].enabled = !!enabled else delete apps[id].enabled - await this.repo.saveConfig( + await this.git.saveConfig( `env/apps/${id}.yaml`, `env/apps/secrets.${id}.yaml`, { apps }, @@ -1900,7 +1882,7 @@ export default class OtomiStack { async saveSettings(secretPaths?: string[]): Promise { const settings = cloneDeep(this.getSettings()) as Record> settings.otomi.nodeSelector = arrayToObject(settings.otomi.nodeSelector as []) - await this.repo.saveConfig( + await this.git.saveConfig( 'env/settings.yaml', `env/secrets.settings.yaml`, omit(settings, ['cluster']), @@ -1909,20 +1891,20 @@ export default class OtomiStack { } async saveUsers(): Promise { - const users = this.db.getCollection('users') as Array + const users = this.repoService.getUsers() const relativePath = `env/secrets.users.yaml` - const { secretFilePostfix } = this.repo + const { secretFilePostfix } = this.git let secretRelativePath = `${relativePath}${secretFilePostfix}` if (secretFilePostfix) { - const secretExists = await this.repo.fileExists(relativePath) + const secretExists = await this.git.fileExists(relativePath) if (!secretExists) secretRelativePath = relativePath } const outData: Record = set({}, `users`, users) debug(`Saving users`) - await this.repo.writeFile(secretRelativePath, outData, false) + await this.git.writeFile(secretRelativePath, outData, false) if (users.length === 0) { - await this.repo.removeFile(relativePath) - await this.repo.removeFile(secretRelativePath) + await this.git.removeFile(relativePath) + await this.git.removeFile(secretRelativePath) } } @@ -1940,7 +1922,7 @@ export default class OtomiStack { }), ) const values = set({}, 'teamConfig', teamValues) - await this.repo.saveConfig(filePath, secretFilePath, values, secretPaths ?? this.getSecretPaths()) + await this.git.saveConfig(filePath, secretFilePath, values, secretPaths ?? this.getSecretPaths()) } async saveTeamSealedSecrets(teamId: string, data: any, id: string): Promise { @@ -1950,52 +1932,52 @@ export default class OtomiStack { } async saveTeamBackups(teamId: string): Promise { - const backups = this.db.getCollection('backups', { teamId }) as Array + const backups = this.repoService.getTeamConfigService(teamId).getBackups() const cleaneBackups: Array> = backups.map((obj) => { return omit(obj, ['teamId']) }) const relativePath = getTeamBackupsFilePath(teamId) const outData: Record = set({}, getTeamBackupsJsonPath(teamId), cleaneBackups) debug(`Saving backups of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) } async saveTeamNetpols(teamId: string): Promise { - const netpols = this.db.getCollection('netpols', { teamId }) as Array + const netpols = this.repoService.getTeamConfigService(teamId).getNetpols() const cleaneNetpols: Array> = netpols.map((obj) => { return omit(obj, ['teamId']) }) const relativePath = getTeamNetpolsFilePath(teamId) const outData: Record = set({}, getTeamNetpolsJsonPath(teamId), cleaneNetpols) debug(`Saving network policies of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) } async saveTeamWorkloads(teamId: string): Promise { - const workloads = this.db.getCollection('workloads', { teamId }) as Array + const workloads = this.repoService.getTeamConfigService(teamId).getWorkloads() const cleaneWorkloads: Array> = workloads.map((obj) => { return omit(obj, ['teamId']) }) const relativePath = getTeamWorkloadsFilePath(teamId) const outData: Record = set({}, getTeamWorkloadsJsonPath(teamId), cleaneWorkloads) debug(`Saving workloads of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) await Promise.all( workloads.map((workload) => { - this.saveWorkloadValues(workload) + this.saveWorkloadValues(teamId, workload) }), ) } async saveTeamProjects(teamId: string): Promise { - const projects = this.db.getCollection('projects', { teamId }) as Array + const projects = this.repoService.getTeamConfigService(teamId).getProjects() const cleaneProjects: Array> = projects.map((obj) => { return omit(obj, ['teamId']) }) const relativePath = getTeamProjectsFilePath(teamId) const outData: Record = set({}, getTeamProjectsJsonPath(teamId), cleaneProjects) debug(`Saving projects of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) } async saveTeamCoderepos(teamId: string): Promise { @@ -2010,14 +1992,14 @@ export default class OtomiStack { } async saveTeamBuilds(teamId: string): Promise { - const builds = this.db.getCollection('builds', { teamId }) as Array + const builds = this.repoService.getTeamConfigService(teamId).getBuilds() const cleaneBuilds: Array> = builds.map((obj) => { return omit(obj, ['teamId']) }) const relativePath = getTeamBuildsFilePath(teamId) const outData: Record = set({}, getTeamBuildsJsonPath(teamId), cleaneBuilds) debug(`Saving builds of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) } async saveTeamPolicies(teamId: string): Promise { @@ -2025,21 +2007,21 @@ export default class OtomiStack { const relativePath = getTeamPoliciesFilePath(teamId) const outData: Record = set({}, getTeamPoliciesJsonPath(teamId), policies) debug(`Saving policies of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) + await this.git.writeFile(relativePath, outData) } - async saveWorkloadValues(workload: Workload): Promise { - debug(`Saving workload values: id: ${workload.id!} teamId: ${workload.teamId!} name: ${workload.name}`) - const data = this.getWorkloadValues(workload.id!) + async saveWorkloadValues(teamId: string, workload: Workload): Promise { + debug(`Saving workload values: id: ${workload.id!} teamId: ${teamId} name: ${workload.name}`) + const data = this.getWorkloadValues(teamId, workload.id!) const outData = omit(data, ['id', 'teamId', 'name']) as Record outData.values = stringifyYaml(data.values, undefined, 4) - const path = getTeamWorkloadValuesFilePath(workload.teamId!, workload.name) + const path = getTeamWorkloadValuesFilePath(teamId, workload.name) - await this.repo.writeFile(path, outData, false) + await this.git.writeFile(path, outData, false) } async saveTeamServices(teamId: string): Promise { - const services = this.db.getCollection('services', { teamId }) + const services = this.repoService.getTeamConfigService(teamId).getServices() const data = {} const values: any[] = [] services.forEach((service) => { @@ -2049,7 +2031,7 @@ export default class OtomiStack { set(data, getTeamServicesJsonPath(teamId), values) const filePath = getTeamServicesFilePath(teamId) - await this.repo.writeFile(filePath, data) + await this.git.writeFile(filePath, data) } async loadTeam(inTeam: Team): Promise { @@ -2060,26 +2042,7 @@ export default class OtomiStack { debug(`Loaded team: ${res.id!}`) } - loadSecret(inSecret, teamId): void { - const secret: Record = omit(inSecret, ...secretTransferProps) - secret.teamId = teamId - secret.secret = secretTransferProps.reduce((memo: any, prop) => { - if (inSecret[prop] !== undefined) memo[prop] = inSecret[prop] - return memo - }, {}) - const res: any = this.db.populateItem('secrets', secret, { teamId, name: secret.name }, secret.id as string) - debug(`Loaded secret: name: ${res.name}, id: ${res.id}, teamId: ${teamId}`) - } - - convertDbSecretToValues(inSecret: any): any { - const secret: any = omit(inSecret, 'secret') - secretTransferProps.forEach((prop) => { - if (inSecret.secret[prop] !== undefined) secret[prop] = inSecret.secret[prop] - }) - return secret - } - - loadService(svcRaw, teamId): void { + loadService(svcRaw, teamId: string): void { // Create service const svc = omit( svcRaw, @@ -2098,6 +2061,7 @@ export default class OtomiStack { 'cname', ) svc.teamId = teamId + svc.name = svcRaw.name as string if (!('name' in svcRaw)) debug('Unknown service structure') if (svcRaw.type === 'cluster') svc.ingress = { type: 'cluster' } else { @@ -2106,7 +2070,6 @@ export default class OtomiStack { // TODO remove the isArray check in 0.5.24 const headers = isArray(svcRaw.headers) ? undefined : svcRaw.headers svc.ingress = { - certArn: svcRaw.certArn || undefined, certName: svcRaw.certName || undefined, domain: url.domain, headers, @@ -2123,7 +2086,7 @@ export default class OtomiStack { } } - const res: any = this.db.populateItem('services', removeBlankAttributes(svc), undefined, svc.id as string) + const res: any = this.repoService.getTeamConfigService(teamId).createService(removeBlankAttributes(svc)) debug(`Loaded service: name: ${res.name}, id: ${res.id}`) } @@ -2152,7 +2115,7 @@ export default class OtomiStack { async getSession(user: k8s.User): Promise { const rootStack = await getSessionStack() const valuesSchema = await getValuesSchema() - const currentSha = rootStack.repo.commitSha + const currentSha = rootStack.git.commitSha const { obj } = this.getSettings(['obj']) const regions = await getRegions() const objStorageRegions = @@ -2163,7 +2126,7 @@ export default class OtomiStack { const data: Session = { ca: env.CUSTOM_ROOT_CA, core: this.getCore() as Record, - corrupt: rootStack.repo.corrupt, + corrupt: rootStack.git.corrupt, editor: this.editor, inactivityTimeout: env.EDITOR_INACTIVITY_TIMEOUT, user: user as SessionUser, diff --git a/src/playground.ts b/src/playground.ts new file mode 100644 index 000000000..dcad87973 --- /dev/null +++ b/src/playground.ts @@ -0,0 +1,17 @@ +#!/usr/bin/env node --nolazy -r ts-node/register -r tsconfig-paths/register + +import { loadValues } from './repo' +import { Repo } from './otomi-models' + +async function play() { + // Suppose your environment directory is "my-environment" + const envDir = '/private/tmp/otomi-bootstrap-dev' + + const allSpecs = await loadValues(envDir) + + const repo = allSpecs as Repo + console.log(allSpecs) + console.log(repo) +} + +play() diff --git a/src/repo.ts b/src/repo.ts old mode 100644 new mode 100755 index 6f8070989..b8f752003 --- a/src/repo.ts +++ b/src/repo.ts @@ -1,410 +1,346 @@ -import axios, { AxiosResponse } from 'axios' -import Debug from 'debug' -import diff from 'deep-diff' -import { copy, ensureDir, pathExists, readFile, writeFile } from 'fs-extra' -import { unlink } from 'fs/promises' -import { glob } from 'glob' -import stringifyJson from 'json-stable-stringify' -import { cloneDeep, get, isEmpty, merge, set, unset } from 'lodash' -import { basename, dirname, join } from 'path' -import simpleGit, { CheckRepoActions, CleanOptions, CommitResult, ResetMode, SimpleGit } from 'simple-git' -import { GIT_BRANCH, GIT_LOCAL_PATH, GIT_PASSWORD, GIT_REPO_URL, GIT_USER, TOOLS_HOST, cleanEnv } from 'src/validators' -import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' -import { BASEURL } from './constants' -import { GitPullError, HttpError, ValidationError } from './error' -import { DbMessage, getIo } from './middleware' -import { Core } from './otomi-models' -import { removeBlankAttributes } from './utils' - -const debug = Debug('otomi:repo') - -const env = cleanEnv({ - GIT_BRANCH, - GIT_LOCAL_PATH, - GIT_PASSWORD, - GIT_REPO_URL, - GIT_USER, - TOOLS_HOST, -}) - -const baseUrl = BASEURL -const prepareUrl = `${baseUrl}/prepare` -const initUrl = `${baseUrl}/init` -const valuesUrl = `${baseUrl}/otomi/values` - -const getProtocol = (url): string => (url && url.includes('://') ? url.split('://')[0] : 'https') - -const getUrl = (url): string => (!url || url.includes('://') ? url : `${getProtocol(url)}://${url}`) - -function getUrlAuth(url, user, password): string | undefined { - if (!url) return - const protocol = getProtocol(url) - const [_, bareUrl] = url.split('://') - const encodedUser = encodeURIComponent(user as string) - const encodedPassword = encodeURIComponent(password as string) - return protocol === 'file' ? `${protocol}://${bareUrl}` : `${protocol}://${encodedUser}:${encodedPassword}@${bareUrl}` +import { globSync } from 'glob' +import { get, merge, set } from 'lodash' +import cloneDeep from 'lodash/cloneDeep' +import { loadYaml } from './utils' +import path from 'path' + +export interface FileMap { + envDir: string + kind: + | 'AplCoreApp' + | 'AplAlerts' + | 'AplCluster' + | 'AplDatabase' + | 'AplDns' + | 'AplIngress' + | 'AplObjectStorage' + | 'AplKms' + | 'AplIdentityProvider' + | 'AplCapabilities' + | 'AplSmtp' + | 'AplBackups' + | 'AplUser' + | 'AplTeamBuild' + | 'AplTeamPolicy' + | 'AplTeamSettings' + | 'AplTeamNetworkControl' + | 'AplTeamProject' + | 'AplTeamBackup' + | 'AplTeamSecret' + | 'AplTeamService' + | 'AplTeamWorkload' + | 'AplTeamTools' + | 'AplVersions' + jsonPathExpression: string + pathGlob: string + processAs: 'arrayItem' | 'mapItem' + resourceGroup: 'team' | 'platformSettings' | 'platformApps' | 'platformDatabases' | 'platformBackups' | 'users' + resourceDir: string } -const secretFileRegex = new RegExp(`^(.*/)?secrets.*.yaml(.dec)?$`) -export class Repo { - branch: string - commitSha: string - corrupt = false - email: string - git: SimpleGit - password: string - path: string - remote: string - remoteBranch: string - secretFilePostfix = '' - url: string | undefined - urlAuth: string | undefined - user: string - - constructor( - path: string, - url: string | undefined, - user: string, - email: string, - urlAuth: string | undefined, - branch: string | undefined, - ) { - this.branch = branch || 'main' - this.email = email - this.path = path - this.remote = 'origin' - this.remoteBranch = join(this.remote, this.branch) - this.urlAuth = urlAuth - this.user = user - this.url = url - this.git = simpleGit(this.path) - } - - getProtocol() { - return getProtocol(this.url) - } - - async requestInitValues(): Promise { - debug(`Tools: requesting "init" on values repo path ${this.path}`) - const res = await axios.get(initUrl, { params: { envDir: this.path } }) - return res - } - - async requestPrepareValues(): Promise { - debug(`Tools: requesting "prepare" on values repo path ${this.path}`) - const res = await axios.get(prepareUrl, { params: { envDir: this.path } }) - return res - } - - async requestValues(params): Promise { - debug(`Tools: requesting "otomi/values" ${this.path}`) - const res = await axios.get(valuesUrl, { params: { envDir: this.path, ...params } }) - return res - } - async addConfig(): Promise { - debug(`Adding git config`) - await this.git.addConfig('user.name', this.user) - await this.git.addConfig('user.email', this.email) - if (this.isRootClone()) { - if (this.getProtocol() === 'file') { - // tell the the git repo there to accept updates even when it is checked out - const _git = simpleGit(this.url!.replace('file://', '')) - await _git.addConfig('receive.denyCurrentBranch', 'updateInstead') - } - // same for the root repo, which needs to accept pushes from children - await this.git.addConfig('receive.denyCurrentBranch', 'updateInstead') - } - } - - async init(bare = true): Promise { - await this.git.init(bare !== undefined ? bare : this.isRootClone()) - await this.git.addRemote(this.remote, this.url!) - } - - async initSops(): Promise { - if (this.secretFilePostfix === '.dec') return - this.secretFilePostfix = (await pathExists(join(this.path, '.sops.yaml'))) ? '.dec' : '' - } - - getSafePath(file: string): string { - if (this.secretFilePostfix === '') return file - // otherwise we might have to give *.dec variant for secrets - if (file.match(secretFileRegex) && !file.endsWith(this.secretFilePostfix)) return `${file}${this.secretFilePostfix}` - return file - } - - async removeFile(file: string): Promise { - const absolutePath = join(this.path, file) - const exists = await this.fileExists(file) - if (exists) { - debug(`Removing file: ${absolutePath}`) - // Remove empty secret file due to https://github.com/mozilla/sops/issues/926 issue - await unlink(absolutePath) - } - if (file.match(secretFileRegex)) { - // also remove the encrypted file as they are operated on in pairs - const encFile = `${file}${this.secretFilePostfix}` - if (await this.fileExists(encFile)) { - const absolutePathEnc = join(this.path, encFile) - debug(`Removing enc file: ${absolutePathEnc}`) - await unlink(absolutePathEnc) - } - } - } - - async diffFile(file: string, data: Record): Promise { - const repoFile: string = this.getSafePath(file) - const oldData = await this.readFile(repoFile) - const deepDiff = diff(data, oldData) - debug(`Diff for ${file}: `, deepDiff) - return deepDiff - } - - async writeFile(file: string, data: Record, unsetBlankAttributes = true): Promise { - let cleanedData = data - if (unsetBlankAttributes) cleanedData = removeBlankAttributes(data, { emptyArrays: true }) - if (isEmpty(cleanedData) && file.match(secretFileRegex)) { - // remove empty secrets file which sops can't handle - return this.removeFile(file) - } - // we also bail when no changes found - const hasDiff = await this.diffFile(file, data) - if (!hasDiff) return - // ok, write new content - const absolutePath = join(this.path, file) - debug(`Writing to file: ${absolutePath}`) - const sortedData = JSON.parse(stringifyJson(data) as string) - const content = isEmpty(sortedData) ? '' : stringifyYaml(sortedData, undefined, 4) - const dir = dirname(absolutePath) - await ensureDir(dir) - await writeFile(absolutePath, content, 'utf8') - } - - async fileExists(relativePath: string): Promise { - const absolutePath = join(this.path, relativePath) - return await pathExists(absolutePath) - } - - async readDir(relativePath: string): Promise { - const absolutePath = join(this.path, relativePath) - const files = await glob([`${absolutePath}/**/*.yaml`]) - const filenames = files.map((file) => basename(file)) - return filenames - } - - async readFile(file: string, checkSuffix = false): Promise> { - if (!(await this.fileExists(file))) return {} - const safeFile = checkSuffix ? this.getSafePath(file) : file - const absolutePath = join(this.path, safeFile) - debug(`Reading from file: ${absolutePath}`) - const doc = parseYaml(await readFile(absolutePath, 'utf8')) || {} - return doc - } - - async loadConfig(file: string, secretFile: string): Promise { - const data = await this.readFile(file) - const secretData = await this.readFile(secretFile, true) - return merge(data, secretData) as Core - } - - async saveConfig( - dataPath: string, - inSecretRelativeFilePath: string, - config: Record, - secretJsonPaths: string[], - ): Promise> { - const secretData = {} - const plainData = cloneDeep(config) - secretJsonPaths.forEach((objectPath) => { - const val = get(config, objectPath) - if (val) { - set(secretData, objectPath, val) - unset(plainData, objectPath) - } - }) - - let secretDataRelativePath = `${inSecretRelativeFilePath}${this.secretFilePostfix}` - - if (this.secretFilePostfix) { - const secretExists = await this.fileExists(inSecretRelativeFilePath) - // In case secret file does not exists, create new one and let sops to encrypt it in place - if (!secretExists) secretDataRelativePath = inSecretRelativeFilePath - } - - await this.writeFile(secretDataRelativePath, secretData) - await this.writeFile(dataPath, plainData) - } +export const getFileMaps = (envDir: string): Array => { + return [ + { + kind: 'AplCoreApp', + envDir, + jsonPathExpression: '$.apps.*', + pathGlob: `${envDir}/env/apps/*.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformApps', + resourceDir: 'apps', + }, + { + envDir, + kind: 'AplAlerts', + jsonPathExpression: '$.alerts', + pathGlob: `${envDir}/env/settings/*alerts.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplCluster', + envDir, + jsonPathExpression: '$.cluster', + pathGlob: `${envDir}/env/settings/cluster.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplDatabase', + envDir, + jsonPathExpression: '$.databases.*', + pathGlob: `${envDir}/env/databases/*.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformDatabases', + resourceDir: 'databases', + }, + { + kind: 'AplDns', + envDir, + jsonPathExpression: '$.dns', + pathGlob: `${envDir}/env/settings/*dns.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplIngress', + envDir, + jsonPathExpression: '$.ingress', + pathGlob: `${envDir}/env/settings/ingress.yaml`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplKms', + envDir, + jsonPathExpression: '$.kms', + pathGlob: `${envDir}/env/settings/*kms.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplObjectStorage', + envDir, + jsonPathExpression: '$.obj', + pathGlob: `${envDir}/env/settings/*obj.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplIdentityProvider', + envDir, + jsonPathExpression: '$.oidc', + pathGlob: `${envDir}/env/settings/*oidc.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplCapabilities', + envDir, + jsonPathExpression: '$.otomi', + pathGlob: `${envDir}/env/settings/*otomi.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplBackups', + envDir, + jsonPathExpression: '$.platformBackups', + pathGlob: `${envDir}/env/settings/*platformBackups.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformBackups', + resourceDir: 'settings', + }, + { + kind: 'AplSmtp', + envDir, + jsonPathExpression: '$.smtp', + pathGlob: `${envDir}/env/settings/*smtp.{yaml,yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplUser', + envDir, + jsonPathExpression: '$.users[*]', + pathGlob: `${envDir}/env/users/*.{yaml,yaml.dec}`, + processAs: 'arrayItem', + resourceGroup: 'users', + resourceDir: 'users', + }, + { + kind: 'AplVersions', + envDir, + jsonPathExpression: '$.versions', + pathGlob: `${envDir}/env/settings/versions.yaml`, + processAs: 'mapItem', + resourceGroup: 'platformSettings', + resourceDir: 'settings', + }, + { + kind: 'AplTeamBuild', + envDir, + jsonPathExpression: '$.teamConfig.*.builds[*]', + pathGlob: `${envDir}/env/teams/*/builds/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'builds', + }, + { + kind: 'AplTeamWorkload', + envDir, + jsonPathExpression: '$.teamConfig.*.workloads[*]', + pathGlob: `${envDir}/env/teams/*/workloads/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'workloads', + }, + { + kind: 'AplTeamService', + envDir, + jsonPathExpression: '$.teamConfig.*.services[*]', + pathGlob: `${envDir}/env/teams/*/services/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'services', + }, + { + kind: 'AplTeamSecret', + envDir, + jsonPathExpression: '$.teamConfig.*.sealedsecrets[*]', + pathGlob: `${envDir}/env/teams/*/sealedsecrets/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'sealedsecrets', + }, + { + kind: 'AplTeamBackup', + envDir, + jsonPathExpression: '$.teamConfig.*.backups[*]', + pathGlob: `${envDir}/env/teams/*/backups/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'backups', + }, + { + kind: 'AplTeamProject', + envDir, + jsonPathExpression: '$.teamConfig.*.projects[*]', + pathGlob: `${envDir}/env/teams/*/projects/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'projects', + }, + { + kind: 'AplTeamNetworkControl', + envDir, + jsonPathExpression: '$.teamConfig.*.netpols[*]', + pathGlob: `${envDir}/env/teams/*/netpols/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'netpols', + }, + { + kind: 'AplTeamSettings', + envDir, + jsonPathExpression: '$.teamConfig.*.settings', + pathGlob: `${envDir}/env/teams/*/*settings{.yaml,.yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'team', + resourceDir: '.', + }, + { + kind: 'AplTeamTools', + envDir, + jsonPathExpression: '$.teamConfig.*.apps', + pathGlob: `${envDir}/env/teams/*/*apps{.yaml,.yaml.dec}`, + processAs: 'mapItem', + resourceGroup: 'team', + resourceDir: '.', + }, + { + kind: 'AplTeamPolicy', + envDir, + jsonPathExpression: '$.teamConfig.*.policies', + pathGlob: `${envDir}/env/teams/*/policies.yaml`, + processAs: 'mapItem', + resourceGroup: 'team', + resourceDir: '.', + }, + ] +} - isRootClone(): boolean { - return this.path === env.GIT_LOCAL_PATH - } +export const extractTeamDirectory = (filePath: string): string => { + const match = filePath.match(/\/teams\/([^/]+)/) + if (match === null) throw new Error(`Cannot extract team name from ${filePath} string`) + return match[1] +} - hasRemote(): boolean { - return !!env.GIT_REPO_URL - } +export const getJsonPath = (fileMap: FileMap, filePath: string): string => { + let { jsonPathExpression: jsonPath } = fileMap - async initFromTestFolder(): Promise { - // we inflate GIT_LOCAL_PATH from the ./test folder - debug(`DEV mode: using local folder values`) - await copy(join(process.cwd(), 'test'), env.GIT_LOCAL_PATH, { - recursive: true, - overwrite: false, - }) - await this.init(false) - await this.git.checkoutLocalBranch(this.branch) - await this.git.add('.') - await this.addConfig() - await this.git.commit('initial commit', undefined, this.getOptions()) + if (fileMap.resourceGroup === 'team') { + const teamName = extractTeamDirectory(filePath) + jsonPath = jsonPath.replace('teamConfig.*', `teamConfig.${teamName}`) } - async clone(): Promise { - debug(`Checking if local git repository exists at: ${this.path}`) - const isRepo = await this.git.checkIsRepo(CheckRepoActions.IS_REPO_ROOT) - // remote root url - this.url = getUrl(`${env.GIT_REPO_URL}`) - if (!isRepo) { - debug(`Initializing repo...`) - if (!this.hasRemote() && this.isRootClone()) return await this.initFromTestFolder() - else if (!this.isRootClone()) { - // child clone, point to remote root - this.urlAuth = getUrlAuth(this.url, env.GIT_USER, env.GIT_PASSWORD) - } - debug(`Cloning from '${this.url}' to '${this.path}'`) - await this.git.clone(this.urlAuth!, this.path) - await this.addConfig() - await this.git.checkout(this.branch) - } else if (this.url) { - debug('Repo already exists. Checking out correct branch.') - // Git fetch ensures that local git repository is synced with remote repository - await this.git.fetch({}) - await this.git.checkout(this.branch) - } - this.commitSha = await this.getCommitSha() + if (jsonPath.includes('.*')) { + const fileName = path.basename(filePath, path.extname(filePath)) + const strippedFileName = fileName.replace(/^secrets\.|\.yaml|\.dec$/g, '') + jsonPath = jsonPath.replace('.*', `.${strippedFileName}`) } + if (jsonPath.includes('[*]')) jsonPath = jsonPath.replace('[*]', '') + jsonPath = jsonPath.replace('$.', '') + return jsonPath +} - getOptions() { - const options = {} - if (env.isDev) options['--no-verify'] = null // only for dev do we have git hooks blocking direct commit - return options +export const loadFileToSpec = async ( + filePath: string, + fileMap: FileMap, + spec: Record, + deps = { loadYaml }, +): Promise => { + const jsonPath = getJsonPath(fileMap, filePath) + const data = await deps.loadYaml(filePath) + if (fileMap.processAs === 'arrayItem') { + const ref: Record[] = get(spec, jsonPath) + ref.push(data?.spec) + } else { + const ref: Record = get(spec, jsonPath) + // Decrypted secrets may need to be merged with plain text specs + const newRef = merge(cloneDeep(ref), data?.spec) + set(spec, jsonPath, newRef) } +} - async commit(editor: string): Promise { - await this.git.add('./*') - const summary = await this.git.commit(`otomi-api commit by ${editor}`, undefined, this.getOptions()) - debug(`Commit summary: ${JSON.stringify(summary)}`) - return summary - } +export const initSpec = (fileMap: FileMap, jsonPath: string, spec: Record) => { + if (fileMap.processAs === 'arrayItem') set(spec, jsonPath, []) + else set(spec, jsonPath, {}) +} - async pull(skipRequest = false, skipMsg = false): Promise { - // test root can't pull as it has no remote - if (!this.url) return - debug('Pulling') - try { - const summary = await this.git.pull(this.remote, this.branch, { '--rebase': 'true' }) - const summJson = JSON.stringify(summary) - debug(`Pull summary: ${summJson}`) - this.commitSha = await this.getCommitSha() - if (!skipRequest) await this.requestInitValues() - await this.initSops() - } catch (e) { - debug('Could not pull from remote. Upstream commits? Marked db as corrupt.', e) - this.corrupt = true - if (!skipMsg) { - const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'conflict' } - getIo().emit('db', msg) - } - try { - // Remove local changes so that no conflict can happen - debug('Removing local changes.') - await this.git.reset(ResetMode.HARD) - debug(`Go to ${this.branch} branch`) - await this.git.checkout(this.branch) - debug('Removing local changes.') - await this.git.reset(ResetMode.HARD) - debug('Cleaning local values and directories.') - await this.git.clean(CleanOptions.FORCE, ['-d']) - debug('Get the latest branch from:', this.remote) - await this.git.fetch(this.remote, this.branch) - debug('Reconciling divergent branches.') - await this.git.merge([`${this.remote}/${this.branch}`, '--strategy-option=theirs']) - debug('Trying to remove upstream commits: ', this.remote) - await this.git.push([this.remote, this.branch, '--force']) - } catch (error) { - debug('Failed to remove upstream commits: ', error) - throw new GitPullError('Failed to remove upstream commits!') - } - debug('Removed upstream commits!') - const cleanMsg: DbMessage = { editor: 'system', state: 'clean', reason: 'restored' } - getIo().emit('db', cleanMsg) - this.corrupt = false - } - } +export const hasCorrespondingDecryptedFile = (filePath: string, fileList: Array): boolean => { + return fileList.includes(`${filePath}.dec`) +} - async push(): Promise { - if (!this.url && this.isRootClone()) return - debug('Pushing') - const summary = await this.git.push(this.remote, this.branch) - debug('Pushed. Summary: ', summary) - return +export const loadToSpec = async ( + spec: Record, + fileMap: FileMap, + deps = { loadFileToSpec }, +): Promise => { + const globOptions = { + nodir: true, // Exclude directories + dot: false, } + const files: string[] = globSync(fileMap.pathGlob, globOptions).sort() + const promises: Promise[] = [] - async getCommitSha(): Promise { - return this.git.revparse('HEAD') - } + files.forEach((filePath) => { + const jsonPath = getJsonPath(fileMap, filePath) + initSpec(fileMap, jsonPath, spec) + if (hasCorrespondingDecryptedFile(filePath, files)) return + promises.push(deps.loadFileToSpec(filePath, fileMap, spec)) + }) - async save(editor: string): Promise { - // prepare values first - try { - await this.requestPrepareValues() - } catch (e) { - debug(`ERROR: ${JSON.stringify(e)}`) - if (e.response) { - const { status } = e.response as AxiosResponse - if (status === 422) throw new ValidationError() - throw HttpError.fromCode(status) - } - throw new HttpError(500, `${e}`) - } - // all good? commit - await this.commit(editor) - try { - // we are in a unique developer branch, so we can pull, push, and merge - // with the remote root, which might have been modified by another developer - // since this is a child branch, we don't need to re-init - // retry up to 3 times to pull and push if there are conflicts - const skipInit = true - const retries = 3 - for (let attempt = 1; attempt <= retries; attempt++) { - try { - await this.pull(skipInit) - await this.push() - break - } catch (error) { - if (attempt === retries) throw error - debug(`Attempt ${attempt} failed. Retrying...`) - } - } - } catch (e) { - debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) - debug(`Merge error: ${JSON.stringify(e)}`) - throw new GitPullError() - } - } + await Promise.all(promises) } -export default async function getRepo( - path: string, - url: string, - user: string, - email: string, - password: string, - branch: string, - method: 'clone' | 'init' = 'clone', -): Promise { - await ensureDir(path, { mode: 0o744 }) - const urlNormalized = getUrl(url) - const urlAuth = getUrlAuth(urlNormalized, user, password) - const repo = new Repo(path, urlNormalized, user, email, urlAuth, branch) - await repo[method]() - return repo +export const loadValues = async (envDir: string, deps = { loadToSpec }): Promise> => { + const fileMaps = getFileMaps(envDir) + const spec = {} + + await Promise.all( + fileMaps.map(async (fileMap) => { + await deps.loadToSpec(spec, fileMap) + }), + ) + return spec } diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts new file mode 100644 index 000000000..107c088df --- /dev/null +++ b/src/services/RepoService.ts @@ -0,0 +1,443 @@ +import { + Alerts, + Build, + Cluster, + Dns, + Ingress, + Kms, + Netpol, + Oidc, + Otomi, + Policies, + Project, + Repo, + SealedSecret, + Service, + Settings, + Smtp, + Team, + TeamConfig, + User, + Workload, +} from '../otomi-models' +import { TeamConfigService } from './TeamConfigService' +import { find, flatMap, has, map, mapValues, merge, remove } from 'lodash' +import { v4 as uuidv4 } from 'uuid' + +export class RepoService { + // We can create an LRU cache if needed with a lot of teams. + private teamConfigServiceCache = new Map() + + constructor(private repo: Repo) { + this.repo.apps ??= {} + this.repo.alerts ??= {} as Alerts + this.repo.cluster ??= {} as Cluster + this.repo.databases ??= {} + this.repo.dns ??= {} as Dns + this.repo.ingress ??= {} as Ingress + this.repo.kms ??= {} as Kms + this.repo.obj ??= {} + this.repo.oidc ??= {} as Oidc + this.repo.otomi ??= {} as Otomi + this.repo.platformBackups ??= {} + this.repo.smtp ??= {} as Smtp + this.repo.users ??= [] + this.repo.versions ??= {} + this.repo.teamConfig ??= {} + } + + public getTeamConfigService(teamId: string): TeamConfigService { + if (!this.repo.teamConfig[teamId]) { + throw new Error(`TeamConfig for ${teamId} does not exist.`) + } + + // Check if we already have an instance cached + if (!this.teamConfigServiceCache.has(teamId)) { + // If not, create a new one and store it in the cache + this.teamConfigServiceCache.set(teamId, new TeamConfigService(this.repo.teamConfig[teamId])) + } + + // Return the cached instance + return this.teamConfigServiceCache.get(teamId)! + } + + // ===================================== + // == APPS CRUD (Dictionary) == + // ===================================== + + public createApp(key: string, app: any) { + if (has(this.repo.apps, key)) { + throw new Error(`App[${key}] already exists.`) + } + const newApp = { ...app, id: app.id ?? uuidv4() } + this.repo.apps[key] = newApp + return newApp + } + + public getApp(key: string): any | undefined { + return this.repo.apps[key] + } + + public updateApp(key: string, updates: any): void { + if (!has(this.repo.apps, key)) { + throw new Error(`App[${key}] does not exist.`) + } + merge(this.repo.apps[key], updates) + } + + public deleteApp(key: string): void { + if (!has(this.repo.apps, key)) { + throw new Error(`App[${key}] does not exist.`) + } + delete this.repo.apps[key] + } + + // ===================================== + // == USERS CRUD (Array) == + // ===================================== + + public createUser(user: User): User { + const newUser = { ...user, id: user.id ?? uuidv4() } + if (find(this.repo.users, { id: newUser.id })) { + throw new Error(`User[${user.id}] already exists.`) + } + this.repo.users.push(newUser) + return newUser + } + + public getUser(id: string): User { + const user = find(this.repo.users, { id }) + if (!user) { + throw new Error(`User[${id}] does not exist.`) + } + return user + } + + public getUsers(): User[] { + return this.repo.users + } + + public getUsersEmail(): string[] { + return map(this.repo.users, 'email') + } + + public updateUser(id: string, updates: Partial): User { + const user = find(this.repo.users, { id }) + if (!user) throw new Error(`User[${id}] does not exist.`) + return merge(user, updates) + } + + public deleteUser(id: string): void { + remove(this.repo.users, { id }) + } + + // ===================================== + // == TEAM CONFIG CRUD (Dictionary) == + // ===================================== + private getDefaultTeamConfig(): TeamConfig { + return { + builds: [], + workloads: [], + services: [], + sealedSecrets: [], + backups: [], + projects: [], + netpols: [], + settings: {} as Team, + apps: {}, + policies: {} as Policies, + workloadValues: [], + } + } + + public createTeamConfig(teamId: string, team: Team): TeamConfig { + if (has(this.repo.teamConfig, teamId)) { + throw new Error(`TeamConfig[${teamId}] already exists.`) + } + const newTeam = merge({}, this.getDefaultTeamConfig(), team) + newTeam.id ??= uuidv4() + this.repo.teamConfig[teamId] = newTeam + return this.repo.teamConfig[teamId] + } + + public getTeamConfig(teamId: string): TeamConfig | undefined { + return this.repo.teamConfig[teamId] + } + + public updateTeamConfig(teamId: string, updates: Partial): void { + if (!has(this.repo.teamConfig, teamId)) { + throw new Error(`TeamConfig[${teamId}] does not exist.`) + } + merge(this.repo.teamConfig[teamId], updates) + } + + public deleteTeamConfig(teamId: string): void { + if (!has(this.repo.teamConfig, teamId)) { + throw new Error(`TeamConfig[${teamId}] does not exist.`) + } + delete this.repo.teamConfig[teamId] + } + + // ===================================== + // == SINGLE OBJECT CRUD (alerts, cluster, dns, etc.) == + // ===================================== + + public getAlerts(): Alerts { + return this.repo.alerts + } + + public updateAlerts(updates: Partial): void { + merge(this.repo.alerts, updates) + } + + public getCluster(): Cluster { + return this.repo.cluster + } + + public updateCluster(updates: Partial): void { + merge(this.repo.cluster, updates) + } + + public getDns(): Dns { + return this.repo.dns + } + + public updateDns(updates: Partial): void { + merge(this.repo.dns, updates) + } + + public getIngress(): Ingress { + return this.repo.ingress + } + + public updateIngress(updates: Partial): void { + merge(this.repo.ingress, updates) + } + + public getKms(): Kms { + return this.repo.kms + } + + public updateKms(updates: Partial): void { + if (!this.repo.kms) { + throw new Error(`KMS object does not exist.`) + } + merge(this.repo.kms, updates) + } + + public getOidc(): Oidc { + return this.repo.oidc + } + + public updateOidc(updates: Partial): void { + merge(this.repo.oidc, updates) + } + + public getOtomi(): Otomi { + return this.repo.otomi + } + + public updateOtomi(updates: Partial): void { + merge(this.repo.otomi, updates) + } + + public getSmtp(): Smtp { + return this.repo.smtp + } + + public updateSmtp(updates: Partial): void { + merge(this.repo.smtp, updates) + } + + // ===================================== + // == OTHER DICTIONARIES (databases, obj, versions, etc.) == + // ===================================== + + public createDatabase(key: string, database: any) { + if (has(this.repo.databases, key)) { + throw new Error(`Database[${key}] already exists.`) + } + const newDatabase = { ...database, id: database.id ?? uuidv4() } + + this.repo.databases[key] = newDatabase + return newDatabase + } + + public getDatabase(key: string): any | undefined { + return this.repo.databases[key] + } + + public updateDatabase(key: string, updates: any): void { + if (!has(this.repo.databases, key)) { + throw new Error(`Database[${key}] does not exist.`) + } + merge(this.repo.databases[key], updates) + } + + public deleteDatabase(key: string): void { + if (!has(this.repo.databases, key)) { + throw new Error(`Database[${key}] does not exist.`) + } + delete this.repo.databases[key] + } + + public createVersion(key: string, version: any) { + if (has(this.repo.versions, key)) { + throw new Error(`Version[${key}] already exists.`) + } + const newVersion = { ...version, id: version.id ?? uuidv4() } + this.repo.versions[key] = newVersion + return newVersion + } + + public getVersion(key: string): any | undefined { + return this.repo.versions[key] + } + + public updateVersion(key: string, updates: any): void { + if (!has(this.repo.versions, key)) { + throw new Error(`Version[${key}] does not exist.`) + } + merge(this.repo.versions[key], updates) + } + + public deleteVersion(key: string): void { + if (!has(this.repo.versions, key)) { + throw new Error(`Version[${key}] does not exist.`) + } + delete this.repo.versions[key] + } + + public createObj(key: string, obj: any) { + if (has(this.repo.obj, key)) { + throw new Error(`Obj[${key}] already exists.`) + } + const newObj = { ...obj, id: obj.id ?? uuidv4() } + this.repo.obj[key] = newObj + return newObj + } + + public getObj(): any | undefined { + return this.repo.obj + } + + public updateObj(key: string, updates: any): void { + if (!has(this.repo.obj, key)) { + throw new Error(`Obj[${key}] does not exist.`) + } + merge(this.repo.obj[key], updates) + } + + public deleteObj(key: string): void { + if (!has(this.repo.obj, key)) { + throw new Error(`Obj[${key}] does not exist.`) + } + delete this.repo.obj[key] + } + + public createPlatformBackups(key: string, platformBackup: any) { + if (has(this.repo.platformBackups, key)) { + throw new Error(`PlatformBackups[${key}] already exists.`) + } + const newBackup = { ...platformBackup, id: platformBackup.id ?? uuidv4() } + this.repo.platformBackups[key] = newBackup + return newBackup + } + + public getPlatformBackups(): any | undefined { + return this.repo.platformBackups + } + + public getPlatformBackup(key: string): any | undefined { + return this.repo.platformBackups[key] + } + + public updatePlatformBackups(key: string, updates: any): void { + if (!has(this.repo.platformBackups, key)) { + throw new Error(`PlatformBackups[${key}] does not exist.`) + } + merge(this.repo.platformBackups[key], updates) + } + + public deletePlatformBackups(key: string): void { + if (!has(this.repo.platformBackups, key)) { + throw new Error(`Obj[${key}] does not exist.`) + } + delete this.repo.platformBackups[key] + } + + public getSettings(): Settings { + return { + alerts: this.repo.alerts, + cluster: this.repo.cluster, + dns: this.repo.dns, + ingress: this.repo.ingress, + kms: this.repo.kms, + obj: this.repo.obj, + oidc: this.repo.oidc, + otomi: this.repo.otomi, + platformBackups: this.repo.platformBackups, + smtp: this.repo.smtp, + } as Settings + } + + public updateSettings(updates: Partial): void { + merge(this.repo, updates) + } + + public getRepo(): Repo { + return this.repo + } + + public setRepo(repo: Repo): void { + this.repo = repo + } + + public getAllTeamSettings(): Team[] { + return map(this.repo.teamConfig, 'settings') + } + + public getAllNetpols(): Netpol[] { + return flatMap(this.repo.teamConfig, 'netpols') + } + + public getAllProjects(): Project[] { + return flatMap(this.repo.teamConfig, 'projects') + } + + public getAllBuilds(): Build[] { + return flatMap(this.repo.teamConfig, 'builds') + } + + public getAllPolicies(): Record { + return mapValues(this.repo.teamConfig, 'policies') + } + + public getAllWorkloads(): Workload[] { + return flatMap(this.repo.teamConfig, 'workloads') + } + + public getAllServices(): Service[] { + return flatMap(this.repo.teamConfig, 'services') + } + + public getAllSealedSecrets(): SealedSecret[] { + return flatMap(this.repo.teamConfig, 'sealedSecrets') + } + + /** Retrieve a collection dynamically from the Repo */ + public getCollection(collectionId: string): any { + if (!has(this.repo, collectionId)) { + throw new Error(`Getting repo collection [${collectionId}] does not exist.`) + } + return this.repo[collectionId] + } + + /** Update a collection dynamically in the Repo */ + public updateCollection(collectionId: string, data: any): void { + if (!has(this.repo, collectionId)) { + throw new Error(`Updating repo collection [${collectionId}] does not exist.`) + } + merge(this.repo[collectionId], data) + } +} diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts new file mode 100644 index 000000000..3273c070a --- /dev/null +++ b/src/services/TeamConfigService.ts @@ -0,0 +1,392 @@ +import { + Backup, + Build, + Netpol, + Policies, + Policy, + Project, + SealedSecret, + Service, + Team, + TeamConfig, + Workload, + WorkloadValues, +} from '../otomi-models' +import { find, has, merge, remove } from 'lodash' +import { NotExistError } from '../error' +import { v4 as uuidv4 } from 'uuid' + +export class TeamConfigService { + constructor(private teamConfig: TeamConfig) {} + + // ===================================== + // == BUILDS CRUD == + // ===================================== + + public createBuild(build: Build): Build { + const newBuild = { ...build, id: build.id ?? uuidv4() } + if (find(this.teamConfig.builds, { id: newBuild.id })) { + throw new Error(`Build[${newBuild.id}] already exists.`) + } + this.teamConfig.builds.push(newBuild) + return newBuild + } + + public getBuild(id: string): Build { + const build = find(this.teamConfig.builds, { id }) + if (!build) { + throw new NotExistError(`Build[${id}] does not exist.`) + } + return build + } + + public getBuilds(): Build[] { + return this.teamConfig.builds + } + + public updateBuild(id: string, updates: Partial): Build { + const build = find(this.teamConfig.builds, { id }) + if (!build) throw new Error(`Build[${id}] does not exist.`) + return merge(build, updates) + } + + public deleteBuild(id: string): void { + remove(this.teamConfig.builds, { id }) + } + + // ===================================== + // == WORKLOADS CRUD == + // ===================================== + + public createWorkload(workload: Workload): Workload { + const newWorkload = { ...workload, id: workload.id ?? uuidv4() } + if (find(this.teamConfig.workloads, { id: newWorkload.id })) { + throw new Error(`Workload[${newWorkload.id}] already exists.`) + } + + this.teamConfig.workloads.push(newWorkload) + return newWorkload + } + + public getWorkload(id: string): Workload { + const workload = find(this.teamConfig.workloads, { id }) + if (!workload) { + throw new NotExistError(`Workload[${id}] does not exist.`) + } + return workload + } + + public getWorkloads(): Workload[] { + return this.teamConfig.workloads + } + + public updateWorkload(id: string, updates: Partial): Workload { + const workload = find(this.teamConfig.workloads, { id }) + if (!workload) throw new Error(`Workload[${id}] does not exist.`) + return merge(workload, updates) + } + + public deleteWorkload(id: string): void { + remove(this.teamConfig.workloads, { id }) + } + + // ===================================== + // == WORKLOADVALUES CRUD == + // ===================================== + + public createWorkloadValues(workloadValues: WorkloadValues): WorkloadValues { + const newWorkloadValues = { ...workloadValues, id: workloadValues.id ?? uuidv4() } + if (find(this.teamConfig.workloadValues, { id: newWorkloadValues.id })) { + throw new Error(`WorkloadValues[${newWorkloadValues.id}] already exists.`) + } + this.teamConfig.workloadValues.push(newWorkloadValues) + return newWorkloadValues + } + + public getWorkloadValues(id: string): WorkloadValues { + const workloadValues = find(this.teamConfig.workloadValues, { id }) + if (!workloadValues) { + throw new NotExistError(`WorkloadValues[${id}] does not exist.`) + } + return workloadValues + } + + public updateWorkloadValues(id: string, updates: Partial): WorkloadValues { + const workloadValues = find(this.teamConfig.workloadValues, { id }) + if (!workloadValues) throw new Error(`WorkloadValues[${id}] does not exist.`) + return merge(workloadValues, updates) + } + + public deleteWorkloadValues(id: string): void { + remove(this.teamConfig.workloadValues, { id }) + } + + // ===================================== + // == SERVICES CRUD == + // ===================================== + + public createService(service: Service): Service { + const newService = { ...service, id: service.id ?? uuidv4() } + if (find(this.teamConfig.services, { id: newService.id })) { + throw new Error(`Service[${newService.id}] already exists.`) + } + this.teamConfig.services.push(newService) + return newService + } + + public getService(id: string): Service { + const service = find(this.teamConfig.services, { id }) + if (!service) { + throw new NotExistError(`Service[${id}] does not exist.`) + } + return service + } + + public getServices(): Service[] { + return this.teamConfig.services + } + public updateService(id: string, updates: Partial): Service { + const service = find(this.teamConfig.services, { id }) + if (!service) throw new Error(`Service[${id}] does not exist.`) + return merge(service, updates) + } + + public deleteService(id: string): void { + remove(this.teamConfig.services, { id }) + } + + // ===================================== + // == SEALED SECRETS CRUD == + // ===================================== + + public createSealedSecret(secret: SealedSecret): SealedSecret { + const newSecret = { ...secret, id: secret.id ?? uuidv4() } + if (find(this.teamConfig.sealedSecrets, { id: newSecret.id })) { + throw new Error(`SealedSecret[${newSecret.id}] already exists.`) + } + this.teamConfig.sealedSecrets.push(newSecret) + return newSecret + } + + public getSealedSecret(id: string): SealedSecret { + const sealedSecrets = find(this.teamConfig.sealedSecrets, { id }) + if (!sealedSecrets) { + throw new NotExistError(`SealedSecret[${id}] does not exist.`) + } + return sealedSecrets + } + + public getSealedSecrets(): SealedSecret[] { + return this.teamConfig.sealedSecrets + } + + public updateSealedSecret(id: string, updates: Partial): SealedSecret { + const secret = find(this.teamConfig.sealedSecrets, { id }) + if (!secret) throw new Error(`SealedSecret[${id}] does not exist.`) + return merge(secret, updates) + } + + public deleteSealedSecret(id: string): void { + remove(this.teamConfig.sealedSecrets, { id }) + } + + // ===================================== + // == BACKUPS CRUD == + // ===================================== + + public createBackup(backup: Backup): Backup { + const newBackup = { ...backup, id: backup.id ?? uuidv4() } + if (find(this.teamConfig.backups, { id: newBackup.id })) { + throw new Error(`Backup[${newBackup.id}] already exists.`) + } + this.teamConfig.backups.push(newBackup) + return newBackup + } + + public getBackup(id: string): Backup { + const backup = find(this.teamConfig.backups, { id }) + if (!backup) { + throw new NotExistError(`Backup[${id}] does not exist.`) + } + return backup + } + + public getBackups(): Backup[] { + return this.teamConfig.backups + } + + public updateBackup(id: string, updates: Partial): Backup { + const backup = find(this.teamConfig.backups, { id }) + if (!backup) throw new Error(`Backup[${id}] does not exist.`) + return merge(backup, updates) + } + + public deleteBackup(id: string): void { + remove(this.teamConfig.backups, { id }) + } + + // ===================================== + // == PROJECTS CRUD == + // ===================================== + + public createProject(project: Project): Project { + const newProject = { ...project, id: project.id ?? uuidv4() } + if (find(this.teamConfig.projects, { id: newProject.id })) { + throw new Error(`Project[${newProject.id}] already exists.`) + } + this.teamConfig.projects.push(newProject) + return newProject + } + + public getProject(id: string): Project { + const project = find(this.teamConfig.projects, { id }) + if (!project) { + throw new NotExistError(`Project[${id}] does not exist.`) + } + return project + } + + public getProjects(): Project[] { + return this.teamConfig.projects + } + + public updateProject(id: string, updates: Partial): Project { + const project = find(this.teamConfig.projects, { id }) + if (!project) throw new Error(`Project[${id}] does not exist.`) + return merge(project, updates) + } + + public deleteProject(id: string): void { + remove(this.teamConfig.projects, { id }) + } + + // ===================================== + // == NETPOLS CRUD == + // ===================================== + + public createNetpol(netpol: Netpol): Netpol { + const newNetpol = { ...netpol, id: netpol.id ?? uuidv4() } + if (find(this.teamConfig.netpols, { id: newNetpol.id })) { + throw new Error(`Netpol[${newNetpol.id}] already exists.`) + } + this.teamConfig.netpols.push(newNetpol) + return newNetpol + } + + public getNetpol(id: string): Netpol { + const netpol = find(this.teamConfig.netpols, { id }) + if (!netpol) { + throw new NotExistError(`Netpol[${id}] does not exist.`) + } + return netpol + } + + public getNetpols(): Netpol[] { + return this.teamConfig.netpols + } + + public updateNetpol(id: string, updates: Partial): Netpol { + const netpol = find(this.teamConfig.netpols, { id }) + if (!netpol) { + throw new Error(`Netpol[${id}] does not exist.`) + } + return merge(netpol, updates) + } + + public deleteNetpol(id: string): void { + remove(this.teamConfig.netpols, { id }) + } + + // ===================================== + // == SETTINGS CRUD == + // ===================================== + + public getSettings(): Team { + return this.teamConfig.settings + } + + public updateSettings(updates: Partial): Team { + if (!this.teamConfig.settings) { + this.teamConfig.settings = { name: updates.name || '' } + } + return merge(this.teamConfig.settings, updates) + } + + // ===================================== + // == APPS CRUD == + // ===================================== + + public createApp(key: string, app: any): void { + const newApp = { ...app, id: app.id ?? uuidv4() } + if (has(this.teamConfig.apps, key)) { + throw new Error(`App[${key}] already exists.`) + } + this.teamConfig.apps[key] = newApp + return newApp + } + + public getApp(key: string): any | undefined { + return this.teamConfig.apps[key] + } + + public getApps(): any | undefined { + return this.teamConfig.apps + } + + public updateApp(key: string, updates: any): void { + if (!has(this.teamConfig.apps, key)) { + throw new Error(`App[${key}] does not exist.`) + } + merge(this.teamConfig.apps[key], updates) + } + + public deleteApp(key: string): void { + if (!has(this.teamConfig.apps, key)) { + throw new Error(`App[${key}] does not exist.`) + } + delete this.teamConfig.apps[key] + } + + // ===================================== + // == POLICIES CRUD == + // ===================================== + + public getPolicies(): Policies { + return this.teamConfig.policies + } + + public getPolicy(key: string): Policy { + return this.teamConfig.policies[key] + } + + public updatePolicies(updates: Partial): void { + if (!this.teamConfig.policies) { + this.teamConfig.policies = {} + } + merge(this.teamConfig.policies, updates) + } + + public doesProjectNameExist(name: string): boolean { + return ( + this.teamConfig.builds.some((build) => build.name === name) || + this.teamConfig.workloads.some((workload) => workload.name === name) || + this.teamConfig.services.some((service) => service.name === name) + ) + } + + /** Retrieve a collection dynamically from the Teamconfig */ + public getCollection(collectionId: string): any { + if (!has(this.teamConfig, collectionId)) { + throw new Error(`Getting TeamConfig collection [${collectionId}] does not exist.`) + } + return this.teamConfig[collectionId] + } + + /** Update a collection dynamically in the Teamconfig */ + public updateCollection(collectionId: string, data: any): void { + if (!has(this.teamConfig, collectionId)) { + throw new Error(`Updating TeamConfig collection [${collectionId}] does not exist.`) + } + merge(this.teamConfig[collectionId], data) + } +} diff --git a/src/utils/userUtils.ts b/src/utils/userUtils.ts index 18fe7468a..fc132afcb 100644 --- a/src/utils/userUtils.ts +++ b/src/utils/userUtils.ts @@ -34,7 +34,7 @@ export async function getKeycloakUsers( realm: string, username: string, password: string, -): Promise<{ email: string }[]> { +): Promise { try { const token = await getKeycloakToken(keycloakBaseUrl, realm, username, password) const url = `${keycloakBaseUrl}/admin/realms/${realm}/users` @@ -45,12 +45,10 @@ export async function getKeycloakUsers( }, }) - const users = [] as { email: string }[] + const users = [] as string[] for (const user of response.data) { if (user.username === env.ROOT_KEYCLOAK_USER) continue - users.push({ - email: user.email, - }) + users.push(user.email) } return users } catch (error) { From 008224c666707fca3e98ea9ff02b887fbe558937 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Wed, 19 Feb 2025 08:56:37 +0100 Subject: [PATCH 02/49] feat: update save functions to adhere new file structure --- package-lock.json | 14314 +++++++++++++++++++++++++++- package.json | 1 + src/api/apps/{teamId}/{appId}.ts | 2 +- src/app.ts | 1 - src/git.ts | 47 +- src/middleware/authz.ts | 2 + src/middleware/error.ts | 3 +- src/middleware/jwt.ts | 2 +- src/middleware/session.ts | 1 - src/otomi-models.ts | 5 +- src/otomi-stack.ts | 212 +- src/playground.ts | 13 +- src/repo.ts | 249 +- src/services/RepoService.ts | 50 +- src/services/TeamConfigService.ts | 35 +- src/utils.ts | 20 +- 16 files changed, 14759 insertions(+), 198 deletions(-) diff --git a/package-lock.json b/package-lock.json index e90878aca..c162c64d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "@redkubes/otomi-api", "version": "3.6.0", - "lockfileVersion": 3, + "lockfileVersion": 2, "requires": true, "packages": { "": { @@ -58,6 +58,7 @@ "@types/express": "4.17.13", "@types/fs-extra": "^9.0.13", "@types/jest": "^29.5.14", + "@types/jsonpath": "^0.2.4", "@types/lodash": "4.14.182", "@types/lowdb": "1.0.11", "@types/node": "^16.18.125", @@ -3366,6 +3367,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/jsonpath": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", + "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/jsonwebtoken": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", @@ -9183,9 +9191,9 @@ } }, "node_modules/glob/node_modules/jackspeak": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz", - "integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -18665,5 +18673,14303 @@ "node": ">=10" } } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@apidevtools/json-schema-ref-parser": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz", + "integrity": "sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + } + }, + "@babel/compat-data": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.5.tgz", + "integrity": "sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg==", + "dev": true + }, + "@babel/core": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", + "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/helper-compilation-targets": "^7.26.5", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.7", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.26.7", + "@babel/types": "^7.26.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.5.tgz", + "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", + "dev": true, + "requires": { + "@babel/parser": "^7.26.5", + "@babel/types": "^7.26.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@babel/helper-compilation-targets": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", + "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.26.5", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + } + }, + "@babel/helper-module-transforms": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.26.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", + "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", + "dev": true + }, + "@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "dev": true, + "requires": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" + } + }, + "@babel/parser": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", + "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "dev": true, + "requires": { + "@babel/types": "^7.26.7" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", + "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", + "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", + "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.25.9" + } + }, + "@babel/runtime": { + "version": "7.17.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz", + "integrity": "sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/runtime-corejs3": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.26.0.tgz", + "integrity": "sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==", + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + } + } + }, + "@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + } + }, + "@babel/traverse": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.7.tgz", + "integrity": "sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.5", + "@babel/parser": "^7.26.7", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", + "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@casl/ability": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.3.2.tgz", + "integrity": "sha512-ygOlg3WDu39t1ZOVdDfRpPXEiCn7F/a7uLBJIuAE6KksdBogzPszFRAuGULmo4h37fXIyouYUilVIryh0ddTRA==", + "requires": { + "@ucast/mongo2js": "^1.3.0" + } + }, + "@commitlint/cli": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-16.3.0.tgz", + "integrity": "sha512-P+kvONlfsuTMnxSwWE1H+ZcPMY3STFaHb2kAacsqoIkNx66O0T7sTpBxpxkMrFPyhkJiLJnJWMhk4bbvYD3BMA==", + "dev": true, + "requires": { + "@commitlint/format": "^16.2.1", + "@commitlint/lint": "^16.2.4", + "@commitlint/load": "^16.3.0", + "@commitlint/read": "^16.2.1", + "@commitlint/types": "^16.2.1", + "lodash": "^4.17.19", + "resolve-from": "5.0.0", + "resolve-global": "1.0.0", + "yargs": "^17.0.0" + } + }, + "@commitlint/config-conventional": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-16.2.4.tgz", + "integrity": "sha512-av2UQJa3CuE5P0dzxj/o/B9XVALqYzEViHrMXtDrW9iuflrqCStWBAioijppj9URyz6ONpohJKAtSdgAOE0gkA==", + "dev": true, + "requires": { + "conventional-changelog-conventionalcommits": "^4.3.1" + } + }, + "@commitlint/config-validator": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-16.2.1.tgz", + "integrity": "sha512-hogSe0WGg7CKmp4IfNbdNES3Rq3UEI4XRPB8JL4EPgo/ORq5nrGTVzxJh78omibNuB8Ho4501Czb1Er1MoDWpw==", + "dev": true, + "requires": { + "@commitlint/types": "^16.2.1", + "ajv": "^6.12.6" + } + }, + "@commitlint/ensure": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-16.2.1.tgz", + "integrity": "sha512-/h+lBTgf1r5fhbDNHOViLuej38i3rZqTQnBTk+xEg+ehOwQDXUuissQ5GsYXXqI5uGy+261ew++sT4EA3uBJ+A==", + "dev": true, + "requires": { + "@commitlint/types": "^16.2.1", + "lodash": "^4.17.19" + } + }, + "@commitlint/execute-rule": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-16.2.1.tgz", + "integrity": "sha512-oSls82fmUTLM6cl5V3epdVo4gHhbmBFvCvQGHBRdQ50H/690Uq1Dyd7hXMuKITCIdcnr9umyDkr8r5C6HZDF3g==", + "dev": true + }, + "@commitlint/format": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-16.2.1.tgz", + "integrity": "sha512-Yyio9bdHWmNDRlEJrxHKglamIk3d6hC0NkEUW6Ti6ipEh2g0BAhy8Od6t4vLhdZRa1I2n+gY13foy+tUgk0i1Q==", + "dev": true, + "requires": { + "@commitlint/types": "^16.2.1", + "chalk": "^4.0.0" + } + }, + "@commitlint/is-ignored": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-16.2.4.tgz", + "integrity": "sha512-Lxdq9aOAYCOOOjKi58ulbwK/oBiiKz+7Sq0+/SpFIEFwhHkIVugvDvWjh2VRBXmRC/x5lNcjDcYEwS/uYUvlYQ==", + "dev": true, + "requires": { + "@commitlint/types": "^16.2.1", + "semver": "7.3.7" + } + }, + "@commitlint/lint": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-16.2.4.tgz", + "integrity": "sha512-AUDuwOxb2eGqsXbTMON3imUGkc1jRdtXrbbohiLSCSk3jFVXgJLTMaEcr39pR00N8nE9uZ+V2sYaiILByZVmxQ==", + "dev": true, + "requires": { + "@commitlint/is-ignored": "^16.2.4", + "@commitlint/parse": "^16.2.1", + "@commitlint/rules": "^16.2.4", + "@commitlint/types": "^16.2.1" + } + }, + "@commitlint/load": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-16.3.0.tgz", + "integrity": "sha512-3tykjV/iwbkv2FU9DG+NZ/JqmP0Nm3b7aDwgCNQhhKV5P74JAuByULkafnhn+zsFGypG1qMtI5u+BZoa9APm0A==", + "dev": true, + "requires": { + "@commitlint/config-validator": "^16.2.1", + "@commitlint/execute-rule": "^16.2.1", + "@commitlint/resolve-extends": "^16.2.1", + "@commitlint/types": "^16.2.1", + "@types/node": ">=12", + "chalk": "^4.0.0", + "cosmiconfig": "^7.0.0", + "cosmiconfig-typescript-loader": "^2.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0", + "typescript": "^4.4.3" + } + }, + "@commitlint/message": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-16.2.1.tgz", + "integrity": "sha512-2eWX/47rftViYg7a3axYDdrgwKv32mxbycBJT6OQY/MJM7SUfYNYYvbMFOQFaA4xIVZt7t2Alyqslbl6blVwWw==", + "dev": true + }, + "@commitlint/parse": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-16.2.1.tgz", + "integrity": "sha512-2NP2dDQNL378VZYioLrgGVZhWdnJO4nAxQl5LXwYb08nEcN+cgxHN1dJV8OLJ5uxlGJtDeR8UZZ1mnQ1gSAD/g==", + "dev": true, + "requires": { + "@commitlint/types": "^16.2.1", + "conventional-changelog-angular": "^5.0.11", + "conventional-commits-parser": "^3.2.2" + } + }, + "@commitlint/read": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-16.2.1.tgz", + "integrity": "sha512-tViXGuaxLTrw2r7PiYMQOFA2fueZxnnt0lkOWqKyxT+n2XdEMGYcI9ID5ndJKXnfPGPppD0w/IItKsIXlZ+alw==", + "dev": true, + "requires": { + "@commitlint/top-level": "^16.2.1", + "@commitlint/types": "^16.2.1", + "fs-extra": "^10.0.0", + "git-raw-commits": "^2.0.0" + } + }, + "@commitlint/resolve-extends": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-16.2.1.tgz", + "integrity": "sha512-NbbCMPKTFf2J805kwfP9EO+vV+XvnaHRcBy6ud5dF35dxMsvdJqke54W3XazXF1ZAxC4a3LBy4i/GNVBAthsEg==", + "dev": true, + "requires": { + "@commitlint/config-validator": "^16.2.1", + "@commitlint/types": "^16.2.1", + "import-fresh": "^3.0.0", + "lodash": "^4.17.19", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0" + } + }, + "@commitlint/rules": { + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-16.2.4.tgz", + "integrity": "sha512-rK5rNBIN2ZQNQK+I6trRPK3dWa0MtaTN4xnwOma1qxa4d5wQMQJtScwTZjTJeallFxhOgbNOgr48AMHkdounVg==", + "dev": true, + "requires": { + "@commitlint/ensure": "^16.2.1", + "@commitlint/message": "^16.2.1", + "@commitlint/to-lines": "^16.2.1", + "@commitlint/types": "^16.2.1", + "execa": "^5.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } + } + }, + "@commitlint/to-lines": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-16.2.1.tgz", + "integrity": "sha512-9/VjpYj5j1QeY3eiog1zQWY6axsdWAc0AonUUfyZ7B0MVcRI0R56YsHAfzF6uK/g/WwPZaoe4Lb1QCyDVnpVaQ==", + "dev": true + }, + "@commitlint/top-level": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-16.2.1.tgz", + "integrity": "sha512-lS6GSieHW9y6ePL73ied71Z9bOKyK+Ib9hTkRsB8oZFAyQZcyRwq2w6nIa6Fngir1QW51oKzzaXfJL94qwImyw==", + "dev": true, + "requires": { + "find-up": "^5.0.0" + } + }, + "@commitlint/types": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-16.2.1.tgz", + "integrity": "sha512-7/z7pA7BM0i8XvMSBynO7xsB3mVQPUZbVn6zMIlp/a091XJ3qAXRXc+HwLYhiIdzzS5fuxxNIHZMGHVD4HJxdA==", + "dev": true, + "requires": { + "chalk": "^4.0.0" + } + }, + "@cspell/cspell-bundled-dicts": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-5.21.2.tgz", + "integrity": "sha512-Y5TU6wV/H+RV1VOB32MowiKofBsEZId4x4ReWCyw4KUtJegeljajCfhHwiQaZuvA69E13cJnOMDwi9qozj4kjw==", + "dev": true, + "requires": { + "@cspell/dict-ada": "^2.0.0", + "@cspell/dict-aws": "^2.0.0", + "@cspell/dict-bash": "^2.0.2", + "@cspell/dict-companies": "^2.0.4", + "@cspell/dict-cpp": "^3.1.0", + "@cspell/dict-cryptocurrencies": "^2.0.0", + "@cspell/dict-csharp": "^3.0.1", + "@cspell/dict-css": "^2.0.0", + "@cspell/dict-dart": "^1.1.0", + "@cspell/dict-django": "^2.0.0", + "@cspell/dict-dotnet": "^2.0.1", + "@cspell/dict-elixir": "^2.0.1", + "@cspell/dict-en_us": "^2.2.5", + "@cspell/dict-en-gb": "^1.1.33", + "@cspell/dict-filetypes": "^2.0.1", + "@cspell/dict-fonts": "^2.0.0", + "@cspell/dict-fullstack": "^2.0.5", + "@cspell/dict-git": "^1.0.1", + "@cspell/dict-golang": "^3.0.1", + "@cspell/dict-haskell": "^2.0.0", + "@cspell/dict-html": "^3.0.1", + "@cspell/dict-html-symbol-entities": "^3.0.0", + "@cspell/dict-java": "^2.0.0", + "@cspell/dict-latex": "^2.0.3", + "@cspell/dict-lorem-ipsum": "^2.0.0", + "@cspell/dict-lua": "^2.0.0", + "@cspell/dict-node": "^2.0.1", + "@cspell/dict-npm": "^2.0.3", + "@cspell/dict-php": "^2.0.0", + "@cspell/dict-powershell": "^2.0.0", + "@cspell/dict-public-licenses": "^1.0.4", + "@cspell/dict-python": "^3.0.5", + "@cspell/dict-r": "^1.0.2", + "@cspell/dict-ruby": "^2.0.1", + "@cspell/dict-rust": "^2.0.0", + "@cspell/dict-scala": "^2.0.0", + "@cspell/dict-software-terms": "^2.1.7", + "@cspell/dict-swift": "^1.0.2", + "@cspell/dict-typescript": "^2.0.0", + "@cspell/dict-vue": "^2.0.2" + } + }, + "@cspell/cspell-pipe": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-5.21.2.tgz", + "integrity": "sha512-MN1SXeqqurWYNknbUMPHRFyTvURbO53/1Aw3zEoCeVUSiGbD5rrb1N+t0YDbOphWrkkrJAZk82/2ZBJ2USE/vg==", + "dev": true + }, + "@cspell/cspell-types": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-5.21.2.tgz", + "integrity": "sha512-g2h4qNR6C53IcSM3KR0DZ9gsqp+2FyKD371htJOmSJGmWb4s45QY0hsPr12A2J8/bT+E3uMtHn9KxJeQ7t0SzA==", + "dev": true + }, + "@cspell/dict-ada": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-2.0.0.tgz", + "integrity": "sha512-4gfJEYXVwz6IN2LBaT6QoUV4pqaR35i0z0u9O684vLuVczvNJIHa4vNaSEFBr9d6xxncUyqstgP9P73ajJjh9A==", + "dev": true + }, + "@cspell/dict-aws": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-2.0.0.tgz", + "integrity": "sha512-NKz7pDZ7pwj/b33i3f4WLpC1rOOUMmENwYgftxU+giU2YBeKM2wZbMTSEIzsrel56r0UlQYmdIVlP/B4nnVaoQ==", + "dev": true + }, + "@cspell/dict-bash": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-bash/-/dict-bash-2.0.2.tgz", + "integrity": "sha512-ASIgI/LmV2TYrD4mtk+gm4XmUSTRomOyRt7NDWyBpEww/AeawC2O2NH6FosyUT6dUU3GaXt2wgJRN7R78n1SGg==", + "dev": true + }, + "@cspell/dict-companies": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-2.0.4.tgz", + "integrity": "sha512-nLNVddo+iu4q/Mu03nkVTMnSPxBkoLyZ0MgpHJZWCqxVATbBkzoZNNNjsTkJhvkbrUIWydf8YW4U4wYY+kyh7Q==", + "dev": true + }, + "@cspell/dict-cpp": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-3.1.0.tgz", + "integrity": "sha512-lav99zUQ+iPq6dkQRnTN0+KE9th0UG6Nwl34afyEGJ8CN5Dcq/RJjCVvOkLw6vPvs505xrvQcZW1huftQK8WVg==", + "dev": true + }, + "@cspell/dict-cryptocurrencies": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-cryptocurrencies/-/dict-cryptocurrencies-2.0.0.tgz", + "integrity": "sha512-nREysmmfOp7L2YCRAUufQahwD5/Punzb5AZ6eyg4zUamdRWHgBFphb5/9h2flt1vgdUfhc6hZcML21Ci7iXjaA==", + "dev": true + }, + "@cspell/dict-csharp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-3.0.1.tgz", + "integrity": "sha512-xkfQu03F388w4sdVQSSjrVMkxAxpTYB2yW7nw0XYtTjl3L/jBgvTr/j1BTjdFbQhdNf10Lg0Ak1kXOjmHodVqA==", + "dev": true + }, + "@cspell/dict-css": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-2.0.0.tgz", + "integrity": "sha512-MrFyswFHnPh4H0u6IlV4eHy+ZCUrrHzeL161LyTOqCvaKpbZavMgNYXzZqTF9xafO0iLgwKrl+Gkclu1KVBg0Q==", + "dev": true + }, + "@cspell/dict-dart": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-1.1.0.tgz", + "integrity": "sha512-bBqZINm+RVjMgUrAhRzv/xx3jc3dkIqO0higPbsK+63IAtMNY3EiQnEO4eapbU+qAhyvICY9hZQZXy5Ux4p+Pw==", + "dev": true + }, + "@cspell/dict-django": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-2.0.0.tgz", + "integrity": "sha512-GkJdJv6cmzrKcmq2/oxTXjKF5uv71r4eTqnFmgPbNBW1t+G4VYpzOf0QrVQrhx2RC4DdW5XfcTf+iS0FxHOTmw==", + "dev": true + }, + "@cspell/dict-dotnet": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-2.0.1.tgz", + "integrity": "sha512-b1n4crJRW0WZVf9Gp/52j/tDtjYiZ3N81fIyfqPlBrjsh/5AivfA697DYwQ2mr8ngNX7RsqRtYNQjealA1rEnQ==", + "dev": true + }, + "@cspell/dict-elixir": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-elixir/-/dict-elixir-2.0.1.tgz", + "integrity": "sha512-eTTTxZt1FqGkM780yFDxsGHvTbWqvlK8YISSccK8FyrB6ULW+uflQlNS5AnWg3uWKC48b7pQott+odYCsPJ+Ow==", + "dev": true + }, + "@cspell/dict-en_us": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-2.2.5.tgz", + "integrity": "sha512-gRHem02ZY83AQUTYBxtiVNmtM6gWFCJKumRoAKLj7vWYelmNLcCBsMA3BOOOJ7cZNKCI04lDEdh0u2f2akKZtQ==", + "dev": true + }, + "@cspell/dict-en-gb": { + "version": "1.1.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb/-/dict-en-gb-1.1.33.tgz", + "integrity": "sha512-tKSSUf9BJEV+GJQAYGw5e+ouhEe2ZXE620S7BLKe3ZmpnjlNG9JqlnaBhkIMxKnNFkLY2BP/EARzw31AZnOv4g==", + "dev": true + }, + "@cspell/dict-filetypes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-2.0.1.tgz", + "integrity": "sha512-bQ7K3U/3hKO2lpQjObf0veNP/n50qk5CVezSwApMBckf/sAVvDTR1RGAvYdr+vdQnkdQrk6wYmhbshXi0sLDVg==", + "dev": true + }, + "@cspell/dict-fonts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-2.0.0.tgz", + "integrity": "sha512-AgkTalphfDPtKFPYmEExDcj8rRCh86xlOSXco8tehOEkYVYbksOk9XH0YVH34RFpy93YBd2nnVGLgyGVwagcPw==", + "dev": true + }, + "@cspell/dict-fullstack": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-2.0.5.tgz", + "integrity": "sha512-jnLnHZ4HcCFNUfN+q7m0CUDtISNKat0Jahe1GgnAdEwzcozqKBhlGAjV7mQWPtKpqfJU61JakDnrxzqefAfZHw==", + "dev": true + }, + "@cspell/dict-git": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-1.0.1.tgz", + "integrity": "sha512-Rk+eTof/9inF11lvxmkCRK+gODatA3qai8kSASv6OG/JfPvpj7fTHErx/rdgPw/LOTDUafnoTjTYmj7B2MOQXg==", + "dev": true + }, + "@cspell/dict-golang": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-3.0.1.tgz", + "integrity": "sha512-0KNfXTbxHW2l8iVjxeOf+KFv9Qrw3z5cyKnkuYJWlBTSB5KcUBfeKCb4fsds26VdANqiy6U91b4gDx5kNEmBjQ==", + "dev": true + }, + "@cspell/dict-haskell": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-haskell/-/dict-haskell-2.0.0.tgz", + "integrity": "sha512-cjX1Br+gSWqtcmJD/IMHz1UoP3pUaKIIKy/JfhEs7ANtRt6hhfEKe9dl2kQzDkkKt4pXol+YgdYxL/sVc/nLgQ==", + "dev": true + }, + "@cspell/dict-html": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-3.0.1.tgz", + "integrity": "sha512-sbuFd+nSjgbrGf5eYwSddFhm1eLLePKWyH6Zn8Zb0OODrBK5e4vGn1/scI/MOH5a2IvNs8W9wp84uMBFJcQZtw==", + "dev": true + }, + "@cspell/dict-html-symbol-entities": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-3.0.0.tgz", + "integrity": "sha512-04K7cPTcbYXmHICfiob4gZA1yaj4hpfM+Nl5WIJ1EAZsSGHdqmGEF28GuCjyQ8ZeKiJAsPt/vXuLBbjxkHqZyQ==", + "dev": true + }, + "@cspell/dict-java": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-java/-/dict-java-2.0.0.tgz", + "integrity": "sha512-9f5LDATlAiXRGqxLxgqbOLlQxuMW2zcN7tBgxwtN+4u90vM03ZUOR/gKIuDV/y0ZuAiWBIjA73cjk8DJ13Q1eA==", + "dev": true + }, + "@cspell/dict-latex": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-2.0.3.tgz", + "integrity": "sha512-shNEP20yLLzZ3iURg7241uUKO5vKvoPcd311uiMch0L0qoiC057aV9nawjQIDIgQQZvYT04Mem8N45/jnqg9cA==", + "dev": true + }, + "@cspell/dict-lorem-ipsum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-lorem-ipsum/-/dict-lorem-ipsum-2.0.0.tgz", + "integrity": "sha512-jKogAKtqvgPMleL6usyj3rZ0m8sVUR6drrD+wMnWSfdx1BmUyTsYiuh/mPEfLAebaYHELWSLQG3rDZRvV9Riqg==", + "dev": true + }, + "@cspell/dict-lua": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-lua/-/dict-lua-2.0.0.tgz", + "integrity": "sha512-7WUEBEspSKtsq104WdIys1+DLqAxpJPzw74Py1TuE3fI5GvlzeSZkRFP2ya54GB2lCO4C3mq4M8EnitpibVDfw==", + "dev": true + }, + "@cspell/dict-node": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-2.0.1.tgz", + "integrity": "sha512-ztBWzhvI+YaMehICSJ65cohhjQqoztxf9vrS3YckOiVGBFvUMaFVNdX9klQkvrLcS/O4+2PzoGeIEkmf99amLA==", + "dev": true + }, + "@cspell/dict-npm": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-2.0.3.tgz", + "integrity": "sha512-K/rnVhmHkR3jfdo7o8P2NDKyMcpVe9pUBiFDY1y2C0YqZXIxCz1f5hObm/hxyO+Vbn5VLU3TKU5fZ5z3LspXOg==", + "dev": true + }, + "@cspell/dict-php": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-2.0.0.tgz", + "integrity": "sha512-29WgU77eTO985LvMHwPi1pcpfopfCWfTdffDyqya0JIfOSaFUrlYKzGPkE4mRxcz2G3hXsaM0SRvBNdIRwEdUg==", + "dev": true + }, + "@cspell/dict-powershell": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-powershell/-/dict-powershell-2.0.0.tgz", + "integrity": "sha512-6uvEhLiGmG3u9TFkM1TYcky6aL9Yk7Sk3KJwoTYBaQJY2KqrprgyQtW6yxIw9oU52VRHlq3KKvSAA9Q26+SIkQ==", + "dev": true + }, + "@cspell/dict-public-licenses": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-1.0.4.tgz", + "integrity": "sha512-h4xULfVEDUeWyvp1OO19pcGDqWcBEQ7WGMp3QBHyYpjsamlzsyYYjCRSY2ZvpM7wruDmywSRFmRHJ/+uNFT7nA==", + "dev": true + }, + "@cspell/dict-python": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-3.0.5.tgz", + "integrity": "sha512-0BRxUndYQ2Iom9ZpN5DjUdF2E5fodw1Th/7rlhxxy45fYXUZmfXDvYoaQaymfHP0ZQhH56MCuOUNa/YMcYJtww==", + "dev": true + }, + "@cspell/dict-r": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-r/-/dict-r-1.0.2.tgz", + "integrity": "sha512-Rp3d4sgD6izW9TW5yVI3D//3HTl9oOGBuzTvXRdoHksVPRvzIu2liVhj8MnQ3XIRe5Kc6IhLBAm6izuV2BpGwQ==", + "dev": true + }, + "@cspell/dict-ruby": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-2.0.1.tgz", + "integrity": "sha512-qGqhYfFeoBOashv/l0Kj5o4ilyvfq0s+t+r32juPOkOnbHz+hzxnJo2tMMg/L/UdjVV7Y8ovg4LDBC/seVrMYQ==", + "dev": true + }, + "@cspell/dict-rust": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-2.0.0.tgz", + "integrity": "sha512-EWlQivTKXMU3TTcq/Pi6KPKTQADknasQ700UrxRPzxhwQ4sKVZ88GDu6VZJlsbFUz8Vko289KS6wjiox/7WpmQ==", + "dev": true + }, + "@cspell/dict-scala": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-2.0.0.tgz", + "integrity": "sha512-MUwA2YKpqaQOSR4V1/CVGRNk8Ii5kf6I8Ch+4/BhRZRQXuwWbi21rDRYWPqdQWps7VNzAbbMA+PQDWsD5YY38g==", + "dev": true + }, + "@cspell/dict-software-terms": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-2.1.7.tgz", + "integrity": "sha512-2FW5MlbazmWPFHzMh2xKYFcYnX+E5MdOPRrfYkbVXvWecA0466l54V+op2zlh1CbngMk4eY7AY3qahfWv6gDHA==", + "dev": true + }, + "@cspell/dict-swift": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-swift/-/dict-swift-1.0.2.tgz", + "integrity": "sha512-IrMcRO7AYB2qU5cj4ttZyEbd04DRNOG6Iha106qGGmn4P096m+Y7lOnSLJx/rZbD/cAT3Z/7i465Lr1J93j7yg==", + "dev": true + }, + "@cspell/dict-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-2.0.0.tgz", + "integrity": "sha512-WFBahxsnD2y4Os14tE5Zxh31Ggn4DzGOAu3UoxYl1lLLxaszx4RH7LmAeFuznySboiaBeRBbpfJOjQA796O6VQ==", + "dev": true + }, + "@cspell/dict-vue": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-vue/-/dict-vue-2.0.2.tgz", + "integrity": "sha512-/MB0RS0Gn01s4pgmjy0FvsLfr3RRMrRphEuvTRserNcM8XVtoIVAtrjig/Gg0DPwDrN8Clm0L1j7iQay6S8D0g==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@hkdobrev/run-if-changed": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hkdobrev/run-if-changed/-/run-if-changed-0.3.1.tgz", + "integrity": "sha512-kxu18zdSoTwYpTuwIw4Zsc/cWa+1c/HayXA290TSD97WKBzodntiqm6pPCoXhGDZu2F6/yg5VChJNSjrfMiTSA==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.7", + "execa": "^1.0.0", + "micromatch": "^3.1.10", + "npm-which": "^3.0.1", + "string-argv": "^0.1.1" + }, + "dependencies": { + "cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "requires": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + } + }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", + "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@hutson/parse-repository-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", + "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", + "dev": true + }, + "@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==" + }, + "ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + }, + "string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "requires": { + "ansi-regex": "^6.0.1" + } + }, + "wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + } + } + } + }, + "@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "requires": { + "minipass": "^7.0.4" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + } + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + } + } + }, + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "requires": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + } + }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "dependencies": { + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + } + } + }, + "@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + } + }, + "@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + } + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, + "@jsep-plugin/assignment": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.2.1.tgz", + "integrity": "sha512-gaHqbubTi29aZpVbBlECRpmdia+L5/lh2BwtIJTmtxdbecEyyX/ejAOg7eQDGNvGOUmPY7Z2Yxdy9ioyH/VJeA==", + "requires": {} + }, + "@jsep-plugin/regex": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.3.tgz", + "integrity": "sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug==", + "requires": {} + }, + "@kubernetes/client-node": { + "version": "0.22.1", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.22.1.tgz", + "integrity": "sha512-bNz2uoxJTA/m10h2TsM5e021I9OndpxYYj5hGdHnrmhA+Dk56ohhbbvnJz7TADTEgyBHQ6NAT0QrILypbdFusQ==", + "requires": { + "@types/js-yaml": "^4.0.1", + "@types/node": "^22.0.0", + "@types/request": "^2.47.1", + "@types/ws": "^8.5.3", + "byline": "^5.0.0", + "isomorphic-ws": "^5.0.0", + "js-yaml": "^4.1.0", + "jsonpath-plus": "^10.0.0", + "openid-client": "^5.3.0", + "request": "^2.88.0", + "rfc4648": "^1.3.0", + "stream-buffers": "^3.0.2", + "tar": "^7.0.0", + "tslib": "^2.4.1", + "ws": "^8.18.0" + }, + "dependencies": { + "@types/node": { + "version": "22.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.4.tgz", + "integrity": "sha512-SpNNxkftTJOPk0oN+y2bIqurEXHTA2AOZ3EJDDKeJ5VzkvvORSvmQXGQarcOzWV1ac7DCaPBEdMDxBsM+d8jWw==", + "requires": { + "undici-types": "~6.19.8" + } + }, + "tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==" + }, + "ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "requires": {} + } + } + }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "requires": { + "debug": "^4.1.1" + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, + "@linode/api-v4": { + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@linode/api-v4/-/api-v4-0.129.0.tgz", + "integrity": "sha512-he52JriqkSwZTaymWzLgjcF0kGnk5+tgLGS1qkUm254IfSxWzegM+7VaNEzpfUgJC+uKgdKclQhif1wrV26czw==", + "requires": { + "@linode/validation": "*", + "axios": "~1.7.4", + "ipaddr.js": "^2.0.0", + "yup": "^0.32.9" + }, + "dependencies": { + "axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" + } + } + }, + "@linode/validation": { + "version": "0.55.0", + "resolved": "https://registry.npmjs.org/@linode/validation/-/validation-0.55.0.tgz", + "integrity": "sha512-cEkNWq8NB4HAds1vD4u7Br6CPlkBBQ476chXfhV5V7xIn5U4YY8dZoG9tXZKQB4AgO0pV7YMS7v5qhafMEP2vg==", + "requires": { + "@types/yup": "^0.29.13", + "ipaddr.js": "^2.0.0", + "libphonenumber-js": "^1.10.6", + "yup": "^0.32.9" + }, + "dependencies": { + "ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" + } + } + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true + }, + "@pkgr/utils": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", + "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "is-glob": "^4.0.3", + "open": "^8.4.0", + "picocolors": "^1.0.0", + "tiny-glob": "^0.2.9", + "tslib": "^2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, + "@redocly/ajv": { + "version": "8.6.4", + "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", + "integrity": "sha512-y9qNj0//tZtWB2jfXNK3BX18BSBp9zNR7KE7lMysVHwbZtY392OJCjm6Rb/h4UHH2r1AqjNEHFD6bRn+DqU9Mw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "@redocly/openapi-cli": { + "version": "1.0.0-beta.95", + "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.95.tgz", + "integrity": "sha512-pl/OAeKh/psk6kF9SZjRieJK15T6T5GYcKVeBHvT7vtuhIBRBkrLC3bf3BhiMQx49BdSTB7Tk4/0LFPy0zr1MA==", + "dev": true, + "requires": { + "@redocly/openapi-core": "1.0.0-beta.95", + "@types/node": "^14.11.8", + "assert-node-version": "^1.0.3", + "chokidar": "^3.5.1", + "colorette": "^1.2.0", + "glob": "^7.1.6", + "glob-promise": "^3.4.0", + "handlebars": "^4.7.6", + "portfinder": "^1.0.26", + "simple-websocket": "^9.0.0", + "yargs": "17.0.1" + }, + "dependencies": { + "@types/node": { + "version": "14.18.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.16.tgz", + "integrity": "sha512-X3bUMdK/VmvrWdoTkz+VCn6nwKwrKCFTHtqwBIaQJNx4RUIBBUFXM00bqPz/DsDd+Icjmzm6/tyYZzeGVqb6/Q==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "yargs": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz", + "integrity": "sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "@redocly/openapi-core": { + "version": "1.0.0-beta.95", + "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.0.0-beta.95.tgz", + "integrity": "sha512-7Nnc4Obp/1lbrjNjD33oOnZCuoJa8awhBCEyyayPWGQFp1SkhjpZJnfnKkFuYbQzMjTIAvEeSp9DOQK/E0fgEA==", + "dev": true, + "requires": { + "@redocly/ajv": "^8.6.4", + "@types/node": "^14.11.8", + "colorette": "^1.2.0", + "js-levenshtein": "^1.1.6", + "js-yaml": "^4.1.0", + "lodash.isequal": "^4.5.0", + "minimatch": "^3.0.4", + "node-fetch": "^2.6.1", + "pluralize": "^8.0.0", + "yaml-ast-parser": "0.0.43" + }, + "dependencies": { + "@types/node": { + "version": "14.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.11.tgz", + "integrity": "sha512-zCoCEMA+IPpsRkyCFBqew5vGb7r8RSiB3uwdu/map7uwLAfu1MTazW26/pUDWoNnF88vJz4W3U56i5gtXNqxGg==", + "dev": true + } + } + }, + "@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" + }, + "@sentry/core": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.17.7.tgz", + "integrity": "sha512-SRhLkD05lQb4eCt1ed9Dz72DKbRDlM8PJix8eC2oJLtwyFTS0IlJNkIYRrbsSKkJUm0VsKcDkzIHvUAgBBQICw==", + "requires": { + "@sentry/hub": "6.17.7", + "@sentry/minimal": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/hub": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.17.7.tgz", + "integrity": "sha512-siGzcg+quGOdjRaBGAz6T3ycwHUsGgvalptSJdf5Q783FVFhU+haPul++zGOYURXOgx0RjYGWqagwO8+jljl3Q==", + "requires": { + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/minimal": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.17.7.tgz", + "integrity": "sha512-+/FGem1uXsXikX9wHPw44nevO7YTVjkkiPjyLsvnWMjv64r4Au5s+NQSFHDaytRm9IlU//+OasCAS5VAwHcYRg==", + "requires": { + "@sentry/hub": "6.17.7", + "@sentry/types": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.17.7.tgz", + "integrity": "sha512-YlBhEv8YYUimHLpwzUP5lXkagbGxGAfjvsbahhvqf7rRl8Fu5XbatAcSDcx7YE4R0Iox94IfZy95kF2NL4Idow==", + "requires": { + "@sentry/core": "6.17.7", + "@sentry/hub": "6.17.7", + "@sentry/tracing": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/tracing": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.17.7.tgz", + "integrity": "sha512-QzIDHOjjdi/0LTdrK2LTC27YEOODI473KD8KmMJ+r9PmjDeIjNzz4hJlPwQSnXR3Mu/8foxGJGXsAt3LNmKzlQ==", + "requires": { + "@sentry/hub": "6.17.7", + "@sentry/minimal": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.17.7.tgz", + "integrity": "sha512-iBlJDhrSowZKeqvutY0tCkUjrWqkLFsHrbaQ553r1Nx+/4mxHjzVYtEVGMjZAxQUEbkm0TbnQIkkT7ltglNJ9A==" + }, + "@sentry/utils": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.17.7.tgz", + "integrity": "sha512-HEEEeKlZtwfQvH0waSKv5FKRFjHkVgkkEiAigXoYGQAlaUIuwRTvZGFnsmBoKMIrA4pARkA00FwwdtMU7ziC8A==", + "requires": { + "@sentry/types": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@swagger-api/apidom-ast": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.3.tgz", + "integrity": "sha512-JOXGfadL3ucJH+MY9BDT7dJOwFy0jX3XaAY/CWR92EnliEYfaEzZvH08FGnyqyYHcfT8T0DLKna5CWUHaskZuw==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "@swagger-api/apidom-core": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.3.tgz", + "integrity": "sha512-oRcv3PgwSAvfxvai0afGt/rC2Kk9Zs2ArLPZ6FnVCv/GSnMsuvIQJc5UH29P9eGFcLJIZpQtEHnU6W+u8u0zAA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.0.2", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-error": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.3.tgz", + "integrity": "sha512-cW1tzehphuxA0uM+1m4/0G1d/WjDQyF+RL9D9t1mfhuVxr8AorgYUgY+bjg0pkLfiSTwjrDiuTbYM+jZwrHx8w==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "@swagger-api/apidom-json-pointer": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.3.tgz", + "integrity": "sha512-r6Gvbj2XDcK1wIULoclHcGYPAVXeUkj5ECRslB/Zle/fOU0Jb8s4mmFARyQE/DT+fQggXn8nUJBda3NWPK4GcA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.3.tgz", + "integrity": "sha512-x+NiLR0xZ0VB8AMJr7ii+6A27AP2CGjLyPQr6JutnifXG+vpkjbgXCPyz2qlmrvuLIkBJIE2lBuyX3+qQXmgCw==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.3.tgz", + "integrity": "sha512-9E4/kTf/OzV3vgRjZOB+6TRqQX2ljirD+UBQ8QPSJKBUTtq8+F7U9a8Z9AGYrKCQUMgbge5JMYCqHmOmrJKVUA==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.3.tgz", + "integrity": "sha512-Sc/ywYCHFIMwhZX0Yo+OTmHUvszv3JE3xsvpd18nu7rH+jNyA10oUdTMgnRsTNMnL7siVO+32OKQkdLOSKsEHA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.3.tgz", + "integrity": "sha512-UuGfaJfWzsTCTEyxyKtM86SNdS4EsWB/+j8JWw88h7nFK59YNDmnuXk9PpFyuccpIAHnDq7UJypD3lRvNkJdhQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.3.tgz", + "integrity": "sha512-7Snaf8/qZ3Q9xnjEXo2cJ8L4pvDbHA+k/j7rqbY4o3h5EeMy93ClVUwoeJ2y/JWax/V1DWTyYMhq+9dXlcIUYQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.3.tgz", + "integrity": "sha512-eBNUkQdIDE2fWUXdIeRpN9OMxwfxU2WJFMRHst204Doanh8iJVp3Mz/+z9agHJ6Pkqth2XTXA0EDd1QiI37t+g==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.3.tgz", + "integrity": "sha512-wKMdk5nplkT2PA1sRFZ2WOLmb7xi9++T6UnCeivmV+sy5NtUPpwkJLUWWIlZdZLyiGKmhZQ1gVvhsbyWRoAVPw==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.0-beta.3.tgz", + "integrity": "sha512-XltfOZNTjrBvrWx1hPU6pHn7lHKKY9jXmiQzojX/jhMjZ6Kp6TLGjMMU3SmEUPU6sTaXKUeO5UUTxe2v6VmqMA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-workflows-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-workflows-1/-/apidom-ns-workflows-1-1.0.0-beta.3.tgz", + "integrity": "sha512-+7i8CZAC+TypSYuxTtwXH2qIyQC1ATn8r+1pW4NWCs4F2Yr4K2gGG4ZmOE6ckNa+Q53yyx+Spt7xhLfZDJZp/w==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.0-beta.3.tgz", + "integrity": "sha512-IpnxjLDVdRaY+ewNW8zbiMzYu5eKifpioFPGDlHc2MoTW6zqo5UKViZKL4MbsncySWBj7+URvTIFYjip3TvkKg==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.0-beta.3.tgz", + "integrity": "sha512-Pvj+4OMIzKMx77Ulbp/CdWGAQhor88q5BJlY3cuSNd2Oth+mfe6r7NUXWVSpG6H9+9Y6YJdnGOzQ1PHWJPOlqA==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.0-beta.3.tgz", + "integrity": "sha512-Z8xIy3pirwAapLgZ18BqRVua5rh0NsvQNpx+5Bi5yJD+SD6Syk5OqsgFkqN7T/LmyqpivQiYRgItUBaHXuDnxg==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.0-beta.3.tgz", + "integrity": "sha512-Xl9MU1+24ZTDuGzy/mKVPlnMSvgA/lS+AoqwMzxLMuiIsTmnQX3gEdiM+pXmK7rg1KV/k0aLwDLKt3e00CPiXQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-json": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.0-beta.3.tgz", + "integrity": "sha512-28zQdF8oeaUmNxZNU0De4JUY9jvxiaN+QCJ1GZN9aQ6NQ/eOAuGg+HRuL8+RrSe4STacdi1FCX46jHcMGQeqfg==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.21.1", + "tree-sitter-json": "=0.24.8", + "web-tree-sitter": "=0.24.3" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.0-beta.3.tgz", + "integrity": "sha512-ufiQMl89sTGf09qlh/QvFLEUs9FH9ZZV4mjz1xIB127rnNbWg/sSGr0WIcJGKoLrioI9orb+7aqIhmSDw/plmw==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.0-beta.3.tgz", + "integrity": "sha512-yINlDTIZCywuKRsBeJJDmQLV4+r9FaWDezb4omw6xFQnQZQV1tHgIb549OsV6lT70TabLj+HoMYNLQ9/Bm59Yw==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.0-beta.3.tgz", + "integrity": "sha512-kBZsyNHtp6w41g9N5c+PF4FqoE8vosxgYJEfhQeQs4qXK7T7d8sfjXwcnWRjqlOM4X8dt5R359h58AfwyEF20w==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.0-beta.3.tgz", + "integrity": "sha512-K/FRLCuB0UD9Nq/CNqfjkSVfQfzcpA7lJCg6QueZKd0dQJ54dyHFU9AroshutXHTmEjBleoL7V1K3PNh10HiYQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.0-beta.3.tgz", + "integrity": "sha512-EUdpzJnqZqCu2keEyOxlCED/u0oaA05c6dO48XzbdyENONY/etoN5wrEoqxqxOz+1cC+FZWj/cnmsXdFfbJlEg==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.0-beta.3.tgz", + "integrity": "sha512-2Q9vmrgTQ4cA5WALGyTLp8tF984R9C7QmDOjGf/ngrTIQLyyrQZ0ZDaXL7RHTmT6K9Lg6axMpKquBNiO+Aff6g==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-workflows-json-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-json-1/-/apidom-parser-adapter-workflows-json-1-1.0.0-beta.3.tgz", + "integrity": "sha512-OsKz09YcfQfTbiNZueTLHBrn7umnMjtuN0ZzuNiBs5txaLS196grpzyTiG+4UJ1zIWvjvZmLZEbQqbKZ9qTw8A==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-workflows-yaml-1": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-workflows-yaml-1/-/apidom-parser-adapter-workflows-yaml-1-1.0.0-beta.3.tgz", + "integrity": "sha512-IifK3T6UtqBkIoHOQe6QRGpFU9LFqmJ5T1JzbWnVX+gazoVE+N9ZkFWQfb9pKCaCfAwPVp+vai6bQ2eUsGh4CA==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.3", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.0-beta.3.tgz", + "integrity": "sha512-sSGxnMTNNTqhJBeUOge4Q/5l/7170maoxyrK6J57kRxqkchSAqam73VIBpKa8c/sJ7zhdZI7CZ9aTJe/q7vc7w==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.6.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.21.1", + "web-tree-sitter": "=0.24.3" + } + }, + "@swagger-api/apidom-reference": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.0-beta.3.tgz", + "integrity": "sha512-MkSW/uKA+iCUeQ5HqICGxXPZI1y5vbXnOZLT+22+ZvaO3+5j7tD2aS9mAF+140VaaE5AkpZE28XC9TaYyjEwDg==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-workflows-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-json": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-workflows-json-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-workflows-yaml-1": "^1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.0-beta.3 <1.0.0-rc.0", + "@types/ramda": "~0.30.0", + "axios": "^1.7.4", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "dependencies": { + "axios": { + "version": "1.7.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz", + "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.6.1.tgz", + "integrity": "sha512-FqgUNdtMuPpk5D/9YQvCxTK4tzlUEVq/yNewdcxJbMv0KVt/yDfuuUn5ZvxphftKyOco+1e/6/oNHCKVQ5A83Q==", + "optional": true, + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "@types/cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", + "dev": true + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" + }, + "@types/expect": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-24.3.0.tgz", + "integrity": "sha512-aq5Z+YFBz5o2b6Sp1jigx5nsmoZMK5Ceurjwy6PZmRv7dEi1jLtkARfvB1ME+OXJUG+7TZUDcv3WoCr/aor6dQ==", + "dev": true, + "requires": { + "expect": "*" + } + }, + "@types/express": { + "version": "4.17.13", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", + "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.28", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", + "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/js-yaml": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz", + "integrity": "sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==" + }, + "@types/json-schema": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==" + }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "@types/jsonpath": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@types/jsonpath/-/jsonpath-0.2.4.tgz", + "integrity": "sha512-K3hxB8Blw0qgW6ExKgMbXQv2UPZBoE2GqLpVY+yr7nMD2Pq86lsuIzyAaiQ7eMqFL5B6di6pxSkogLJEyEHoGA==", + "dev": true + }, + "@types/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-c5ltxazpWabia/4UzhIoaDcIza4KViOQhdbjRlfcIGVnsE3c3brkz9Z+F/EeJIECOQP7W7US2hNE930cWWkPiw==", + "requires": { + "@types/node": "*" + } + }, + "@types/lodash": { + "version": "4.14.182", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.182.tgz", + "integrity": "sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q==" + }, + "@types/lowdb": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@types/lowdb/-/lowdb-1.0.11.tgz", + "integrity": "sha512-h99VMxvTuz+VsXUVCCJo4dsps4vbkXwvU71TpmxDoiBU24bJ0VBygIHgmMm+UPoQIFihmV6euRik4z8J7XDJWg==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, + "@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "@types/minimist": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.2.tgz", + "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", + "dev": true + }, + "@types/node": { + "version": "16.18.126", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", + "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==" + }, + "@types/normalize-package-data": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", + "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "dev": true + }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, + "@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", + "dev": true + }, + "@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "requires": { + "types-ramda": "^0.30.1" + } + }, + "@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", + "dev": true + }, + "@types/request": { + "version": "2.48.8", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.8.tgz", + "integrity": "sha512-whjk1EDJPcAR2kYHRbFl/lKeeKYTi05A15K9bnLInCVroNDCtXce57xKdI0/rQaA3K+6q0eFyUBPmqfSndUZdQ==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/semver": { + "version": "7.3.12", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.12.tgz", + "integrity": "sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", + "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "dev": true + }, + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "@types/superagent": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.15.tgz", + "integrity": "sha512-mu/N4uvfDN2zVQQ5AYJI/g4qxn2bHB6521t1UuH09ShNWjebTqN0ZFuYK9uYjcgmI0dTQEs+Owi1EO6U0OkOZQ==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "@types/supertest": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", + "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", + "dev": true, + "requires": { + "@types/superagent": "*" + } + }, + "@types/tough-cookie": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.1.tgz", + "integrity": "sha512-Y0K95ThC3esLEYD6ZuqNek29lNX2EM1qxV8y2FTLUB0ff5wWrk7az+mLrnNFUnaXcgKye22+sFBRXOgpPILZNg==" + }, + "@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "17.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.13.tgz", + "integrity": "sha512-9sWaruZk2JGxIQU+IhI1fhPYRcQ0UuTNuKuCW9bR5fp7qi2Llf7WDzNa17Cy7TKnh3cdxDOiyTu6gaLS0eDatg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "@types/yup": { + "version": "0.29.14", + "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.14.tgz", + "integrity": "sha512-Ynb/CjHhE/Xp/4bhHmQC4U1Ox+I2OpfRYF3dnNgQqn1cHa6LK3H1wJMNPT02tSVZA6FYuXE2ITORfbnb6zBCSA==" + }, + "@typescript-eslint/eslint-plugin": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.41.0.tgz", + "integrity": "sha512-DXUS22Y57/LAFSg3x7Vi6RNAuLpTXwxB9S2nIA7msBb/Zt8p7XqMwdpdc1IU7CkOQUPgAqR5fWvxuKCbneKGmA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/type-utils": "5.41.0", + "@typescript-eslint/utils": "5.41.0", + "debug": "^4.3.4", + "ignore": "^5.2.0", + "regexpp": "^3.2.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.41.0.tgz", + "integrity": "sha512-HQVfix4+RL5YRWZboMD1pUfFN8MpRH4laziWkkAzyO1fvNOY/uinZcvo3QiFJVS/siNHupV8E5+xSwQZrl6PZA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", + "debug": "^4.3.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.41.0.tgz", + "integrity": "sha512-xOxPJCnuktUkY2xoEZBKXO5DBCugFzjrVndKdUnyQr3+9aDWZReKq9MhaoVnbL+maVwWJu/N0SEtrtEUNb62QQ==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.41.0.tgz", + "integrity": "sha512-L30HNvIG6A1Q0R58e4hu4h+fZqaO909UcnnPbwKiN6Rc3BUEx6ez2wgN7aC0cBfcAjZfwkzE+E2PQQ9nEuoqfA==", + "dev": true, + "requires": { + "@typescript-eslint/typescript-estree": "5.41.0", + "@typescript-eslint/utils": "5.41.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@typescript-eslint/types": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.41.0.tgz", + "integrity": "sha512-5BejraMXMC+2UjefDvrH0Fo/eLwZRV6859SXRg+FgbhA0R0l6lDqDGAQYhKbXhPN2ofk2kY5sgGyLNL907UXpA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.41.0.tgz", + "integrity": "sha512-SlzFYRwFSvswzDSQ/zPkIWcHv8O5y42YUskko9c4ki+fV6HATsTODUPbRbcGDFYP86gaJL5xohUEytvyNNcXWg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/visitor-keys": "5.41.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.41.0.tgz", + "integrity": "sha512-QlvfwaN9jaMga9EBazQ+5DDx/4sAdqDkcs05AsQHMaopluVCUyu1bTRUVKzXbgjDlrRAQrYVoi/sXJ9fmG+KLQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.41.0", + "@typescript-eslint/types": "5.41.0", + "@typescript-eslint/typescript-estree": "5.41.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0", + "semver": "^7.3.7" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.41.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.41.0.tgz", + "integrity": "sha512-vilqeHj267v8uzzakbm13HkPMl7cbYpKVjgFWZPIOHIJHZtinvypUhJ5xBXfWYg4eFKqztbMMpOgFpT9Gfx4fw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.41.0", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + } + } + }, + "@ucast/core": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.1.tgz", + "integrity": "sha512-sXKbvQiagjFh2JCpaHUa64P4UdJbOxYeC5xiZFn8y6iYdb0WkismduE+RmiJrIjw/eLDYmIEXiQeIYYowmkcAw==" + }, + "@ucast/js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.2.tgz", + "integrity": "sha512-zxNkdIPVvqJjHI7D/iK8Aai1+59yqU+N7bpHFodVmiTN7ukeNiGGpNmmSjQgsUw7eNcEBnPrZHNzp5UBxwmaPw==", + "requires": { + "@ucast/core": "^1.0.0" + } + }, + "@ucast/mongo": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.2.tgz", + "integrity": "sha512-/zH1TdBJlYGKKD+Wh0oyD+aBvDSWrwHcD8b4tUL9UgHLhzHtkEnMVFuxbw3SRIRsAa01wmy06+LWt+WoZdj1Bw==", + "requires": { + "@ucast/core": "^1.4.1" + } + }, + "@ucast/mongo2js": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.3.tgz", + "integrity": "sha512-sBPtMUYg+hRnYeVYKL+ATm8FaRPdlU9PijMhGYKgsPGjV9J4Ks41ytIjGayvKUnBOEhiCaKUUnY4qPeifdqATw==", + "requires": { + "@ucast/core": "^1.6.1", + "@ucast/js": "^3.0.0", + "@ucast/mongo": "^2.4.0" + } + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "add-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz", + "integrity": "sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=", + "dev": true + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "dependencies": { + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } + } + }, + "ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "requires": { + "string-width": "^4.1.0" + } + }, + "ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "apg-lite": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.4.tgz", + "integrity": "sha512-B32zCN3IdHIc99Vy7V9BaYTUzLeRA8YXYY1aQD1/5I2aqIrO0coi4t6hJPqMisidlBxhyME8UexkHt31SlR6Og==" + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=", + "dev": true + }, + "array-includes": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz", + "integrity": "sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + } + }, + "array-timsort": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", + "integrity": "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "array.prototype.flat": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz", + "integrity": "sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-node-version": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/assert-node-version/-/assert-node-version-1.0.3.tgz", + "integrity": "sha1-yupdG2pY285ZZhII3x4bnkxYD5E=", + "dev": true, + "requires": { + "expected-node-version": "^1.0.0", + "semver": "^5.0.3" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sdk": { + "version": "2.1073.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1073.0.tgz", + "integrity": "sha512-TtyHDL4ZEs8Zh/DqWY/hv745DTWrIwOyBAvfjBJ45RE9h0TjpWqCIowEtb6gRPAKyPPyfGH4s+rEYu07vNK1Hg==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.16.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "axe-core": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.1.tgz", + "integrity": "sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw==", + "dev": true + }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "body-parser": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.1.tgz", + "integrity": "sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA==", + "requires": { + "bytes": "3.1.1", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.9.6", + "raw-body": "2.4.2", + "type-is": "~1.6.18" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "boolean": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", + "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==" + }, + "boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "requires": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + }, + "bytes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.1.tgz", + "integrity": "sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg==" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cachedir": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", + "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + }, + "dependencies": { + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } + }, + "caniuse-lite": { + "version": "1.0.30001697", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001697.tgz", + "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", + "dev": true + }, + "capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "requires": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "clean-deep": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/clean-deep/-/clean-deep-3.4.0.tgz", + "integrity": "sha512-Lo78NV5ItJL/jl+B5w0BycAisaieJGXK1qYi/9m4SjR8zbqmrUtO7Yhro40wEShGmmxs/aJLI/A+jNhdkXK8mw==", + "requires": { + "lodash.isempty": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.transform": "^4.6.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true + }, + "clear-module": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", + "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "dev": true, + "requires": { + "parent-module": "^2.0.0", + "resolve-from": "^5.0.0" + } + }, + "cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true + }, + "cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "requires": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "dev": true + }, + "comment-json": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.2.tgz", + "integrity": "sha512-H8T+kl3nZesZu41zO2oNXIJWojNeK3mHxCLrsBNu6feksBXsgb+PtYz5daP5P86A0F3sz3840KVYehr04enISQ==", + "dev": true, + "requires": { + "array-timsort": "^1.0.3", + "core-util-is": "^1.0.3", + "esprima": "^4.0.1", + "has-own-prop": "^2.0.0", + "repeat-string": "^1.6.1" + } + }, + "commitizen": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.1.tgz", + "integrity": "sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw==", + "dev": true, + "requires": { + "cachedir": "2.3.0", + "cz-conventional-changelog": "3.3.0", + "dedent": "0.7.0", + "detect-indent": "6.1.0", + "find-node-modules": "^2.1.2", + "find-root": "1.1.0", + "fs-extra": "9.1.0", + "glob": "7.2.3", + "inquirer": "8.2.5", + "is-utf8": "^0.2.1", + "lodash": "4.17.21", + "minimist": "1.2.7", + "strip-bom": "4.0.0", + "strip-json-comments": "3.1.1" + }, + "dependencies": { + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + } + }, + "constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "conventional-changelog": { + "version": "3.1.25", + "resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-3.1.25.tgz", + "integrity": "sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==", + "dev": true, + "requires": { + "conventional-changelog-angular": "^5.0.12", + "conventional-changelog-atom": "^2.0.8", + "conventional-changelog-codemirror": "^2.0.8", + "conventional-changelog-conventionalcommits": "^4.5.0", + "conventional-changelog-core": "^4.2.1", + "conventional-changelog-ember": "^2.0.9", + "conventional-changelog-eslint": "^3.0.9", + "conventional-changelog-express": "^2.0.6", + "conventional-changelog-jquery": "^3.0.11", + "conventional-changelog-jshint": "^2.0.9", + "conventional-changelog-preset-loader": "^2.3.4" + } + }, + "conventional-changelog-angular": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz", + "integrity": "sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-atom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-2.0.8.tgz", + "integrity": "sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-codemirror": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-2.0.8.tgz", + "integrity": "sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-config-spec": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-config-spec/-/conventional-changelog-config-spec-2.1.0.tgz", + "integrity": "sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==", + "dev": true + }, + "conventional-changelog-conventionalcommits": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz", + "integrity": "sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "lodash": "^4.17.15", + "q": "^1.5.1" + } + }, + "conventional-changelog-core": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-4.2.4.tgz", + "integrity": "sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==", + "dev": true, + "requires": { + "add-stream": "^1.0.0", + "conventional-changelog-writer": "^5.0.0", + "conventional-commits-parser": "^3.2.0", + "dateformat": "^3.0.0", + "get-pkg-repo": "^4.0.0", + "git-raw-commits": "^2.0.8", + "git-remote-origin-url": "^2.0.0", + "git-semver-tags": "^4.1.1", + "lodash": "^4.17.15", + "normalize-package-data": "^3.0.0", + "q": "^1.5.1", + "read-pkg": "^3.0.0", + "read-pkg-up": "^3.0.0", + "through2": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + } + } + } + }, + "conventional-changelog-ember": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-2.0.9.tgz", + "integrity": "sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-eslint": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-3.0.9.tgz", + "integrity": "sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-express": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-2.0.6.tgz", + "integrity": "sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-jquery": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-3.0.11.tgz", + "integrity": "sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==", + "dev": true, + "requires": { + "q": "^1.5.1" + } + }, + "conventional-changelog-jshint": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-2.0.9.tgz", + "integrity": "sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==", + "dev": true, + "requires": { + "compare-func": "^2.0.0", + "q": "^1.5.1" + } + }, + "conventional-changelog-preset-loader": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-2.3.4.tgz", + "integrity": "sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==", + "dev": true + }, + "conventional-changelog-writer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-5.0.1.tgz", + "integrity": "sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==", + "dev": true, + "requires": { + "conventional-commits-filter": "^2.0.7", + "dateformat": "^3.0.0", + "handlebars": "^4.7.7", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "semver": "^6.0.0", + "split": "^1.0.0", + "through2": "^4.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "conventional-commit-types": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz", + "integrity": "sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg==", + "dev": true + }, + "conventional-commits-filter": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-2.0.7.tgz", + "integrity": "sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==", + "dev": true, + "requires": { + "lodash.ismatch": "^4.4.0", + "modify-values": "^1.0.0" + } + }, + "conventional-commits-parser": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz", + "integrity": "sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==", + "dev": true, + "requires": { + "is-text-path": "^1.0.1", + "JSONStream": "^1.0.4", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + } + }, + "conventional-recommended-bump": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-6.1.0.tgz", + "integrity": "sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==", + "dev": true, + "requires": { + "concat-stream": "^2.0.0", + "conventional-changelog-preset-loader": "^2.3.4", + "conventional-commits-filter": "^2.0.7", + "conventional-commits-parser": "^3.2.0", + "git-raw-commits": "^2.0.8", + "git-semver-tags": "^4.1.1", + "meow": "^8.0.0", + "q": "^1.5.1" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "copyfiles": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", + "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "dev": true, + "requires": { + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "core-js-pure": { + "version": "3.39.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.39.0.tgz", + "integrity": "sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cosmiconfig": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", + "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "dependencies": { + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + } + } + }, + "cosmiconfig-typescript-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-2.0.0.tgz", + "integrity": "sha512-2NlGul/E3vTQEANqPziqkA01vfiuUU8vT0jZAuUIjEW8u3eCcnCQWLggapCjhbF76s7KQF0fM0kXSKmzaDaG1g==", + "dev": true, + "requires": { + "cosmiconfig": "^7", + "ts-node": "^10.7.0" + } + }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true + }, + "cspell": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-5.21.2.tgz", + "integrity": "sha512-yG14BUumeIcsuSEcM//+9XpbUR6a6FlAxfaVI4e5t6ZZE5tPgDE0PNIVr/jAiLPVm9qUfnq+oNdZE8wmVUbMzw==", + "dev": true, + "requires": { + "@cspell/cspell-pipe": "^5.21.2", + "chalk": "^4.1.2", + "commander": "^9.2.0", + "cspell-gitignore": "^5.21.2", + "cspell-glob": "^5.21.2", + "cspell-lib": "^5.21.2", + "fast-json-stable-stringify": "^2.1.0", + "file-entry-cache": "^6.0.1", + "fs-extra": "^10.1.0", + "get-stdin": "^8.0.0", + "glob": "^8.0.3", + "imurmurhash": "^0.1.4", + "semver": "^7.3.7", + "strip-ansi": "^6.0.1", + "vscode-uri": "^3.0.3" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "cspell-gitignore": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-5.21.2.tgz", + "integrity": "sha512-MdNmRRbglmCi20LU7ORZM1gyPSe1gL+4A8Pn+Jm+W5ropSbotzCqiO8BcyhRMNb3lAdMGGrj7gmYtiQ5C/fXIQ==", + "dev": true, + "requires": { + "cspell-glob": "^5.21.2", + "find-up": "^5.0.0" + } + }, + "cspell-glob": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-5.21.2.tgz", + "integrity": "sha512-AabqzG31UWy4CSz1xJIK4qzXcarxuRFP9OD2EX8iDtEo0tQJLGoTHE+UpNDBPWTHearE0BZPhpMDF/radtZAgw==", + "dev": true, + "requires": { + "micromatch": "^4.0.5" + }, + "dependencies": { + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + } + } + }, + "cspell-io": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-5.21.2.tgz", + "integrity": "sha512-3J4cLuN59R7ARiRZ8ke5QwlC5uPfzHLVELOtEAmsTIjuUMvr7BpbrdCuTsUvLkAqYE9NA5eqolqQm3GLXnECNw==", + "dev": true + }, + "cspell-lib": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-5.21.2.tgz", + "integrity": "sha512-emAFXtDfs84FoMlhOxZYxYVvbCoCN0LxN0obIRvCsvFCLUPj9y7vHv/Tu/01ZyAPeo2r6gkqhanJpQyoIDA1yg==", + "dev": true, + "requires": { + "@cspell/cspell-bundled-dicts": "^5.21.2", + "@cspell/cspell-pipe": "^5.21.2", + "@cspell/cspell-types": "^5.21.2", + "clear-module": "^4.1.2", + "comment-json": "^4.2.2", + "configstore": "^5.0.1", + "cosmiconfig": "^7.0.1", + "cspell-glob": "^5.21.2", + "cspell-io": "^5.21.2", + "cspell-trie-lib": "^5.21.2", + "fast-equals": "^3.0.2", + "find-up": "^5.0.0", + "fs-extra": "^10.1.0", + "gensequence": "^3.1.1", + "import-fresh": "^3.3.0", + "resolve-from": "^5.0.0", + "resolve-global": "^1.0.0", + "vscode-languageserver-textdocument": "^1.0.4", + "vscode-uri": "^3.0.3" + } + }, + "cspell-trie-lib": { + "version": "5.21.2", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-5.21.2.tgz", + "integrity": "sha512-iux2F+85jDlBEJZgikfPT5SUZMwuFjNqEJiO1SO+xfQG+2MFV9CaHTsoRJIGNy3udMm1mw0GMY5UIVAodwlnhg==", + "dev": true, + "requires": { + "@cspell/cspell-pipe": "^5.21.2", + "fs-extra": "^10.1.0", + "gensequence": "^3.1.1" + } + }, + "cz-conventional-changelog": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz", + "integrity": "sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw==", + "dev": true, + "requires": { + "@commitlint/load": ">6.1.1", + "chalk": "^2.4.1", + "commitizen": "^4.0.3", + "conventional-commit-types": "^3.0.0", + "lodash.map": "^4.5.1", + "longest": "^2.0.1", + "word-wrap": "^1.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "dargs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", + "dev": true + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true + }, + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, + "deep-diff": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz", + "integrity": "sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==" + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + } + }, + "delay": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true + }, + "detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, + "difunc": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/difunc/-/difunc-0.0.4.tgz", + "integrity": "sha512-zBiL4ALDmviHdoLC0g0G6wVme5bwAow9WfhcZLLopXCAWgg3AEf7RYTs2xugszIGulRHzEVDF/SHl9oyQU07Pw==", + "requires": { + "esprima": "^4.0.0" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "requires": { + "is-obj": "^2.0.0" + } + }, + "dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", + "dev": true + }, + "dotgitignore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", + "integrity": "sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "minimatch": "^3.0.4" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, + "eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.5.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.93.tgz", + "integrity": "sha512-M+29jTcfNNoR9NV7la4SwUqzWAxEwnc7ThA5e1m6LRSotmpfpCpLcIfgtSCVL+MllNLgAyM/5ru86iMRemPzDQ==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} + } + } + }, + "engine.io-client": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz", + "integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3", + "xmlhttprequest-ssl": "~2.0.0" + }, + "dependencies": { + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} + } + } + }, + "engine.io-parser": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", + "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==" + }, + "enhanced-resolve": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", + "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, + "envalid": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/envalid/-/envalid-7.3.1.tgz", + "integrity": "sha512-KL1YRwn8WcoF/Ty7t+yLLtZol01xr9ZJMTjzoGRM8NaSU+nQQjSWOQKKJhJP2P57bpdakJ9jbxqQX4fGTOicZg==", + "requires": { + "tslib": "2.3.1" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz", + "integrity": "sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.1", + "is-string": "^1.0.7", + "is-weakref": "^1.0.1", + "object-inspect": "^1.11.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "requires": { + "prelude-ls": "~1.1.2" + } + } + } + }, + "eslint": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", + "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "dependencies": { + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + } + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "eslint-import-resolver-typescript": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.2.tgz", + "integrity": "sha512-zX4ebnnyXiykjhcBvKIf5TNvt8K7yX6bllTRZ14MiurKPjDpCAZujlszTdB8pcNXhZcOf+god4s9SjQa5GnytQ==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.10.0", + "get-tsconfig": "^4.2.0", + "globby": "^13.1.2", + "is-core-module": "^2.10.0", + "is-glob": "^4.0.3", + "synckit": "^0.8.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "globby": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.2.tgz", + "integrity": "sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==", + "dev": true, + "requires": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.11", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + } + } + }, + "eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "requires": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.26.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz", + "integrity": "sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==", + "dev": true, + "requires": { + "array-includes": "^3.1.4", + "array.prototype.flat": "^1.2.5", + "debug": "^2.6.9", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.6", + "eslint-module-utils": "^2.7.3", + "has": "^1.0.3", + "is-core-module": "^2.8.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.5", + "resolve": "^1.22.0", + "tsconfig-paths": "^3.14.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz", + "integrity": "sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.16.3", + "aria-query": "^4.2.2", + "array-includes": "^3.1.4", + "ast-types-flow": "^0.0.7", + "axe-core": "^4.3.5", + "axobject-query": "^2.2.0", + "damerau-levenshtein": "^1.0.7", + "emoji-regex": "^9.2.2", + "has": "^1.0.3", + "jsx-ast-utils": "^3.2.1", + "language-tags": "^1.0.5", + "minimatch": "^3.0.4" + } + }, + "eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-unused-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-2.0.0.tgz", + "integrity": "sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==", + "dev": true, + "requires": { + "eslint-rule-composer": "^0.3.0" + } + }, + "eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "exec-sh": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", + "dev": true, + "requires": { + "merge": "^1.2.0" + }, + "dependencies": { + "merge": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", + "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", + "dev": true + } + } + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "expected-node-version": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/expected-node-version/-/expected-node-version-1.0.2.tgz", + "integrity": "sha1-uNIlub9nap6H4G29YVtS/J0eOGs=", + "dev": true + }, + "express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz", + "integrity": "sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.6", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==", + "requires": {} + }, + "express-normalize-query-params-middleware": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/express-normalize-query-params-middleware/-/express-normalize-query-params-middleware-0.5.1.tgz", + "integrity": "sha1-2+HoE5rssjT7attcAFnHXblzPSo=" + }, + "express-openapi": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/express-openapi/-/express-openapi-9.3.1.tgz", + "integrity": "sha512-GmI7n7fjom95M0htBwCDFt7wgMLiJeqRRFQ8CVHhKDogGCygeYCgl2Dzbavk2wWwrOaabYMjXG4u3PzvwiALCA==", + "requires": { + "express-normalize-query-params-middleware": "^0.5.0", + "openapi-framework": "^9.3.1", + "openapi-types": "^9.3.1" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + } + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "fast-equals": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-3.0.2.tgz", + "integrity": "sha512-iY0fAmW7fzxHp22VCRLpOgWbsWsF+DJWi1jhc8w+VGlJUiS+KcGZV2A8t+Q9oTQwhG3L1W8Lu/oe3ZyOPdhZjw==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + } + } + }, + "fast-json-patch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.0.tgz", + "integrity": "sha512-IhpytlsVTRndz0hU5t0/MGzS/etxLlfrpG5V5M9mVbuj9TrJLWaMfsox9REM5rkuGX0T+5qjpe8XA1o0gZ42nA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-json-stringify": { + "version": "2.7.13", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-2.7.13.tgz", + "integrity": "sha512-ar+hQ4+OIurUGjSJD1anvYSDcUflywhKjfxnsW4TBTD7+u0tJufv6DKRWoQk3vI6YBOWMoz0TQtfbe7dxbQmvA==", + "requires": { + "ajv": "^6.11.0", + "deepmerge": "^4.2.2", + "rfdc": "^1.2.0", + "string-similarity": "^4.0.1" + } + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "fast-printf": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/fast-printf/-/fast-printf-1.6.9.tgz", + "integrity": "sha512-FChq8hbz65WMj4rstcQsFB0O7Cy++nmbNfLYnD9cYv2cRn8EG6k/MGn9kO/tjO66t09DLDugj3yL+V2o6Qftrg==", + "requires": { + "boolean": "^3.1.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + } + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fill-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/fill-keys/-/fill-keys-1.0.2.tgz", + "integrity": "sha512-tcgI872xXjwFF4xgQmLxi76GnwJG3g/3isB1l4/G5Z4zrbddGpBjqZCO9oEAcB5wX0Hj/5iQB3toxfO7in1hHA==", + "dev": true, + "requires": { + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "find-node-modules": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/find-node-modules/-/find-node-modules-2.1.2.tgz", + "integrity": "sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug==", + "dev": true, + "requires": { + "findup-sync": "^4.0.0", + "merge": "^2.1.0" + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "dev": true, + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + }, + "dependencies": { + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + } + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==" + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "foreachasync": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/foreachasync/-/foreachasync-3.0.0.tgz", + "integrity": "sha1-VQKYfchxS+M5IJfzLgBxyd7gfPY=", + "dev": true + }, + "foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "dependencies": { + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==" + } + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true + }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "dev": true + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fs-routes": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/fs-routes/-/fs-routes-9.0.3.tgz", + "integrity": "sha512-Y5tkylY9fQ1jm11FdJoptzqIG3OyzqrOF16W5odNlIdqFqb2355IbNB3jQkE+C268mSShLmIur8ynYCgL/Yg/g==", + "requires": {} + }, + "fs.extra": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fs.extra/-/fs.extra-1.3.2.tgz", + "integrity": "sha1-3QI/kwE77iRTHxszUUw3sg/ZM0k=", + "dev": true, + "requires": { + "fs-extra": "~0.6.1", + "mkdirp": "~0.3.5", + "walk": "^2.3.9" + }, + "dependencies": { + "fs-extra": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", + "integrity": "sha1-9G8MdbeEH40gCzNIzU1pHVoJnRU=", + "dev": true, + "requires": { + "jsonfile": "~1.0.1", + "mkdirp": "0.3.x", + "ncp": "~0.4.2", + "rimraf": "~2.2.0" + } + }, + "jsonfile": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-1.0.1.tgz", + "integrity": "sha1-6l7+QLg2kLmGZ2FKc5L8YOhCwN0=", + "dev": true + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "generate-password": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.7.1.tgz", + "integrity": "sha512-9bVYY+16m7W7GczRBDqXE+VVuCX+bWNrfYKC/2p2JkZukFb2sKxT6E3zZ3mJGz7GMe5iRK0A/WawSL3jQfJuNQ==" + }, + "gensequence": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/gensequence/-/gensequence-3.1.1.tgz", + "integrity": "sha512-ys3h0hiteRwmY6BsvSttPmkhC0vEQHPJduANBRtH/dlDPZ0UBIb/dXy80IcckXyuQ6LKg+PloRqvGER9IS7F7g==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-pkg-repo": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", + "integrity": "sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==", + "dev": true, + "requires": { + "@hutson/parse-repository-url": "^3.0.0", + "hosted-git-info": "^4.0.0", + "through2": "^2.0.0", + "yargs": "^16.2.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "get-stdin": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-tsconfig": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.2.0.tgz", + "integrity": "sha512-X8u8fREiYOE6S8hLbq99PeykTDoLVnxvF4DjWKJmz9xy2nNRdUcV8ZN9tniJFeKyTU3qnC9lL8n4Chd6LmVKHg==", + "dev": true + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "git-branch-is": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-branch-is/-/git-branch-is-4.0.0.tgz", + "integrity": "sha512-isA1/lMHEaorz2JpcqmSZMnS1EerfMqQdawJ2eebU2MVUI8QWiab0iYrBbs4zaAEPVCPVGYXOGGZVeQFSjCGzg==", + "dev": true, + "requires": { + "commander": "^6.0.0" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + } + } + }, + "git-cz": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/git-cz/-/git-cz-4.9.0.tgz", + "integrity": "sha512-cSRL8IIOXU7UFLdbziCYqg8f8InwLwqHezkiRHNSph7oZqGv0togId1kMTfKil6gzK0VaSXeVBb4oDl0fQCHiw==", + "dev": true + }, + "git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "dev": true, + "requires": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "dev": true, + "requires": { + "gitconfiglocal": "^1.0.0", + "pify": "^2.3.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + } + } + }, + "git-semver-tags": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-4.1.1.tgz", + "integrity": "sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==", + "dev": true, + "requires": { + "meow": "^8.0.0", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "gitconfiglocal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", + "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", + "dev": true, + "requires": { + "ini": "^1.3.2" + } + }, + "glob": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", + "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "jackspeak": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", + "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", + "requires": { + "@isaacs/cliui": "^8.0.2" + } + }, + "lru-cache": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", + "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==" + }, + "minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "requires": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + } + } + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-promise": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/glob-promise/-/glob-promise-3.4.0.tgz", + "integrity": "sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw==", + "dev": true, + "requires": { + "@types/glob": "*" + } + }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globalthis": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", + "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dev": true, + "requires": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4", + "wordwrap": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "hard-rejection": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz", + "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-own-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", + "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true + }, + "header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "requires": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "http-errors": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", + "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" + } + }, + "http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + } + }, + "http-terminator": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/http-terminator/-/http-terminator-3.0.4.tgz", + "integrity": "sha512-9xQ5RFxwBxuGJhFgaBzykLUkSYRPyOb2xcR3EAYzj4Y8WteWmu5zsfEQncxLiyCJQHCdAexjuDcciUqw9W3/Sw==", + "requires": { + "delay": "^5.0.0", + "p-wait-for": "^3.2.0", + "roarr": "^7.0.4", + "type-fest": "^2.3.3" + }, + "dependencies": { + "type-fest": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.11.2.tgz", + "integrity": "sha512-reW2Y2Mpn0QNA/5fvtm5doROLwDPu2zOm5RtY7xQQS05Q7xgC8MOZ3yPNaP9m/s/sNjjFQtHo7VCNqYW2iI+Ig==" + } + } + }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "husky": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", + "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, + "import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "inquirer": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", + "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + } + }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-circular": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-circular/-/is-circular-1.0.2.tgz", + "integrity": "sha512-YttjnrswnUYRVJvxCvu8z+PGMUSzC2JttP0OEXezlAEdp3EXzhf7IZ3j0gRAybJBQupedIZFhY61Tga6E0qASA==" + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-dir": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-dir/-/is-dir-1.0.0.tgz", + "integrity": "sha1-QdN/SV/MrMBaR3jWboMCTCkro/8=" + }, + "is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", + "dev": true + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "requires": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "dependencies": { + "global-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", + "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", + "dev": true, + "requires": { + "ini": "2.0.0" + } + }, + "ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true + } + } + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true + }, + "is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-number-object": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.6.tgz", + "integrity": "sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true + }, + "is-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", + "integrity": "sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA==", + "dev": true + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-promise": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", + "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz", + "integrity": "sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==", + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-text-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", + "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", + "dev": true, + "requires": { + "text-extensions": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "dev": true + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-ws": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz", + "integrity": "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==", + "requires": {} + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "requires": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "dependencies": { + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + } + } + }, + "jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + } + }, + "jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } + } + }, + "jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "requires": {} + } + } + }, + "jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + } + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "dependencies": { + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + } + } + }, + "jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + } + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-mock-extended": { + "version": "4.0.0-beta1", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-4.0.0-beta1.tgz", + "integrity": "sha512-MYcI0wQu3ceNhqKoqAJOdEfsVMamAFqDTjoLN5Y45PAG3iIm4WGnhOu0wpMjlWCexVPO71PMoNir9QrGXrnIlw==", + "dev": true, + "requires": { + "ts-essentials": "^10.0.2" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "requires": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + } + }, + "jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ci-info": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.5.0.tgz", + "integrity": "sha512-yH4RezKOGlOhxkmhbeNuC4eYZKAUsEaGtBuBzDDP1eFUKiccDWzBABxBfOx31IDwDIXMTxWuwAxUGModvkbuVw==", + "dev": true + } + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jmespath": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", + "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==" + }, + "jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "optional": true + }, + "js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "dev": true + }, + "js-sdsl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", + "dev": true + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + } + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsep": { + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.3.9.tgz", + "integrity": "sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw==" + }, + "jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "json-schema-ref-parser": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-4.1.1.tgz", + "integrity": "sha512-lByoCHZ6H2zgb6NtsXIqtzQ+6Ji7iVqnrhWxsXLhF+gXmgu6E8+ErpDxCMR439MUG1nfMjWI2HAoM8l0XgSNhw==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "debug": "^3.1.0", + "js-yaml": "^3.10.0", + "ono": "^4.0.3" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + } + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha512-i/J297TW6xyj7sDFa7AmBPkQvLIxWr2kKPWI26tXydnZrzVAocNqn5DMNT1Mzk0vit1V5UkRM7C1KdVNp7Lmcg==", + "requires": { + "jsonify": "~0.0.0" + } + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "dev": true + }, + "jsonpath": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/jsonpath/-/jsonpath-1.1.1.tgz", + "integrity": "sha512-l6Cg7jRpixfbgoWgkrl77dgEj8RPvND0wMH6TwQmi9Qs4TFfS9u5cUFnbeKTwj5ga5Y3BTGGNI28k117LJ009w==", + "requires": { + "esprima": "1.2.2", + "static-eval": "2.0.2", + "underscore": "1.12.1" + }, + "dependencies": { + "esprima": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.2.2.tgz", + "integrity": "sha512-+JpPZam9w5DuJ3Q67SqsMGtiHKENSMRVoxvArfJZK01/BfLEObtZ6orJa/MtoGNR/rfMgp5837T41PAmTwAv/A==" + } + } + }, + "jsonpath-plus": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", + "integrity": "sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==", + "requires": { + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.9" + } + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.1.tgz", + "integrity": "sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==", + "dev": true, + "requires": { + "array-includes": "^3.1.3", + "object.assign": "^4.1.2" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "jwt-decode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", + "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "language-subtag-registry": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz", + "integrity": "sha512-L0IqwlIXjilBVVYKFT37X9Ih11Um5NEl9cbJIuU/SwP/zEEAbBPOnEeeuxVMf45ydWQRDQN3Nqc96OgbH1K+Pg==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha1-0yHbxNowuovzAk4ED6XBRmH5GTo=", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, + "latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "requires": { + "package-json": "^6.3.0" + } + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "libphonenumber-js": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.13.tgz", + "integrity": "sha512-LIJmXxgs7o1njVZPcX5fkbtcFgDnXXPvJQQBH5Ho/8+r6BFlJaEbJ+bAiaUGaChWUhFtvawwdmXIOz4wZBANCg==" + }, + "lightship": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/lightship/-/lightship-6.8.0.tgz", + "integrity": "sha512-9hNZKgmNiMRVbHappOr9Arw0+StHMT9PmJxQ5f2fqY7o6AmrhxcBKut4pbg4/QhQeDRyQm5TiFVxMDKbDVixCw==", + "requires": { + "@sentry/node": "^6.16.1", + "delay": "^5.0.0", + "express": "^4.17.1", + "http-terminator": "^3.0.4", + "roarr": "^7.8.0", + "serialize-error": "^8.1.0" + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "lint-staged": { + "version": "10.5.4", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz", + "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "cli-truncate": "^2.1.0", + "commander": "^6.2.0", + "cosmiconfig": "^7.0.0", + "debug": "^4.2.0", + "dedent": "^0.7.0", + "enquirer": "^2.3.6", + "execa": "^4.1.0", + "listr2": "^3.2.2", + "log-symbols": "^4.0.0", + "micromatch": "^4.0.2", + "normalize-path": "^3.0.0", + "please-upgrade-node": "^3.2.0", + "string-argv": "0.3.1", + "stringify-object": "^3.3.0" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "string-argv": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", + "integrity": "sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==", + "dev": true + } + } + }, + "listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "requires": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "colorette": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", + "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", + "dev": true + } + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "lodash.isempty": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", + "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=", + "dev": true + }, + "lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" + }, + "lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "lodash.transform": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", + "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "requires": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "dependencies": { + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + } + } + }, + "longest": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-2.0.1.tgz", + "integrity": "sha1-eB4YMpaqlPbU2RbcM10NF676I/g=", + "dev": true + }, + "lowdb": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-1.0.0.tgz", + "integrity": "sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==", + "requires": { + "graceful-fs": "^4.1.3", + "is-promise": "^2.1.0", + "lodash": "4", + "pify": "^3.0.0", + "steno": "^0.4.1" + } + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true + }, + "lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true + }, + "meow": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", + "integrity": "sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==", + "dev": true, + "requires": { + "@types/minimist": "^1.2.0", + "camelcase-keys": "^6.2.2", + "decamelize-keys": "^1.1.0", + "hard-rejection": "^2.1.0", + "minimist-options": "4.1.0", + "normalize-package-data": "^3.0.0", + "read-pkg-up": "^7.0.1", + "redent": "^3.0.0", + "trim-newlines": "^3.0.0", + "type-fest": "^0.18.0", + "yargs-parser": "^20.2.3" + }, + "dependencies": { + "type-fest": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz", + "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==", + "dev": true + } + } + }, + "merge": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz", + "integrity": "sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==", + "dev": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true + }, + "mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" + }, + "mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "requires": { + "mime-db": "1.51.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true + }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, + "minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "requires": { + "lodash": "^4.15.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true + }, + "minimist-options": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz", + "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0", + "kind-of": "^6.0.3" + } + }, + "minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==" + }, + "minizlib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz", + "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==", + "requires": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "requires": { + "glob": "^10.3.7" + } + } + } + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "modify-values": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", + "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==", + "dev": true + }, + "module-not-found-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/module-not-found-error/-/module-not-found-error-1.0.1.tgz", + "integrity": "sha512-pEk4ECWQXV6z2zjhRZUongnLJNUeGQJ3w6OQ5ctGwD+i5o93qjRQUk2Rt6VdNeu3sEP0AB4LcfvdebpxBRVr4g==", + "dev": true + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "mylas": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.13.tgz", + "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", + "dev": true + }, + "nanoclone": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", + "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "ncp": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz", + "integrity": "sha1-q8xsvT7C7Spyn/bnwfqPAXhKhXQ=", + "dev": true + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "nock": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + } + }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node-addon-api": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.2.2.tgz", + "integrity": "sha512-9emqXAKhVoNrQ792nLI/wpzPpJ/bj/YXxW0CvAau1+RdGBcCRF1Dmz7719zgVsQNrzHl9Tzn3ImZ4qWFarWL0A==", + "optional": true + }, + "node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" + }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dev": true, + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "requires": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + } + }, + "node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "optional": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true + }, + "nodemon": { + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.16.tgz", + "integrity": "sha512-zsrcaOfTWRuUzBn3P44RDliLlp263Z/76FPoHFr3cFFkOz0lTPAcIw8dCzfdVIx/t3AtDYCZRCDkoCojJqaG3w==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^3.2.7", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.8", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5", + "update-notifier": "^5.1.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-path": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", + "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", + "dev": true, + "requires": { + "which": "^1.2.10" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + }, + "dependencies": { + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + } + } + }, + "npm-watch": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/npm-watch/-/npm-watch-0.13.0.tgz", + "integrity": "sha512-MYcgocqCzYA44feZhFoYj69FfSaO0EeRE1gcRcmPaXIpNhUMAhNJ1pwic2C4Hn0OPOQmZKSl90CPgmwvOsVhTg==", + "dev": true, + "requires": { + "nodemon": "^3.0.1", + "through2": "^4.0.2" + }, + "dependencies": { + "nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + } + }, + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "npm-which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", + "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", + "dev": true, + "requires": { + "commander": "^2.9.0", + "npm-path": "^2.0.2", + "which": "^1.2.10" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "optional": true + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "optional": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "requires": { + "format-util": "^1.0.3" + } + }, + "open": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", + "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "dev": true, + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "openapi-default-setter": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-default-setter/-/openapi-default-setter-9.3.1.tgz", + "integrity": "sha512-Tc/qpL7H2urJAilmb1wLqHJ8BjjJYudodRFl3RbTkWPSzwalZwPEo4VRP6WnEGvaRgkzLFaT62XgTHRGT8G9lw==", + "requires": { + "openapi-types": "^9.3.1" + } + }, + "openapi-framework": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-framework/-/openapi-framework-9.3.1.tgz", + "integrity": "sha512-cnDQQSHNhwS4jn5IE1gTO4mMTolbPbMf3EzVCMNmEcN09IKGQYmtiVSxxL+uGFiHHlpPfOTSsWrvTCiwbhRtyA==", + "requires": { + "difunc": "0.0.4", + "fs-routes": "^9.0.3", + "glob": "*", + "is-dir": "^1.0.0", + "js-yaml": "^3.10.0", + "openapi-default-setter": "^9.3.1", + "openapi-request-coercer": "^9.3.1", + "openapi-request-validator": "^9.3.1", + "openapi-response-validator": "^9.3.1", + "openapi-schema-validator": "^9.3.1", + "openapi-security-handler": "^9.3.1", + "openapi-types": "^9.3.1", + "ts-log": "^2.1.4" + }, + "dependencies": { + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "openapi-schema-validator": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-9.3.1.tgz", + "integrity": "sha512-5wpFKMoEbUcjiqo16jIen3Cb2+oApSnYZpWn8WQdRO2q/dNQZZl8Pz6ESwCriiyU5AK4i5ZI6+7O3bHQr6+6+g==", + "requires": { + "ajv": "^8.1.0", + "ajv-formats": "^2.0.2", + "lodash.merge": "^4.6.1", + "openapi-types": "^9.3.1" + } + } + } + }, + "openapi-jsonschema-parameters": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-jsonschema-parameters/-/openapi-jsonschema-parameters-9.3.1.tgz", + "integrity": "sha512-WeP/7nh4VAZFhtf5+FbA2HQQPD/a13+xf8uCd3QqpynSzBmcdkstb/kg8Uk4Hmqh5WlNkTGpxnjMxqSw6sHcWw==", + "requires": { + "openapi-types": "^9.3.1" + } + }, + "openapi-path-templating": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-1.6.0.tgz", + "integrity": "sha512-1atBNwOUrZXthTvlvvX8k8ovFEF3iA8mDidYMkdOtvVdndBhTrspbwGXNOzEUaJhm9iUl4Tf5uQaeTLAJvwPig==", + "requires": { + "apg-lite": "^1.0.3" + } + }, + "openapi-request-coercer": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-request-coercer/-/openapi-request-coercer-9.3.1.tgz", + "integrity": "sha512-4rIT/3H5k/zVSXSbxnLQvmHw4BXyCv4ocPjweybDxlVN6ryk+eIaMWiJzFoOf7RI86w++742HOECyzaLoBHftQ==", + "requires": { + "openapi-types": "^9.3.1", + "ts-log": "^2.1.4" + } + }, + "openapi-request-validator": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-request-validator/-/openapi-request-validator-9.3.1.tgz", + "integrity": "sha512-YhX942jTNWH3V/C+ysYcgchyjulT5ZJ1l0c9++isSnF0SKyEiIA+uUiSyAE+pKTc+Oel3ad8mXiy6rFFHoIKaQ==", + "requires": { + "ajv": "^8.3.0", + "ajv-formats": "^2.1.0", + "content-type": "^1.0.4", + "openapi-jsonschema-parameters": "^9.3.1", + "openapi-types": "^9.3.1", + "ts-log": "^2.1.4" + }, + "dependencies": { + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } + } + }, + "openapi-response-validator": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-response-validator/-/openapi-response-validator-9.3.1.tgz", + "integrity": "sha512-2AOzHAbrwdj5DNL3u+BadhfmL3mlc3mmCv6cSAsEjoMncpOOVd95JyMf0j0XUyJigJ8/ILxnhETfg35vt1pGSQ==", + "requires": { + "ajv": "^8.4.0", + "openapi-types": "^9.3.1" + }, + "dependencies": { + "ajv": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.10.0.tgz", + "integrity": "sha512-bzqAEZOjkrUMl2afH8dknrq5KEk2SrwdBROR+vH1EKVQTqaUbJVPdc/gEdggTMM0Se+s+Ja4ju4TlNcStKl2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + } + } + }, + "openapi-schema-validator": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-3.0.3.tgz", + "integrity": "sha512-KKpeNEvAmpy6B2JCfyrM4yWjL6vggDCVbBoR8Yfkj0Jltc6PCW+dBbcg+1yrTCuDv80qBQJ6w0ejA71DlOFegA==", + "dev": true, + "requires": { + "ajv": "^6.5.2", + "lodash.merge": "^4.6.1", + "openapi-types": "1.3.4", + "swagger-schema-official": "2.0.0-bab6bed" + }, + "dependencies": { + "openapi-types": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-1.3.4.tgz", + "integrity": "sha512-h8rADpW3k/wepLdERKF0VKMAPdoFYNQCLGPmc/f8sgQ2dxUy+7sY4WAX2XDUDjhKTjbJVbxxofLkzy7f1/tE4g==", + "dev": true + } + } + }, + "openapi-security-handler": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-security-handler/-/openapi-security-handler-9.3.1.tgz", + "integrity": "sha512-q9rzMCwo3hia/56eBVkCUURQXZBlP/2DzplVd9Gvi3fgCXrgjnp+eY0W9EcmTJU5unb8rOuMExKsEI2cdEziVg==", + "requires": { + "openapi-types": "^9.3.1" + } + }, + "openapi-server-url-templating": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.1.0.tgz", + "integrity": "sha512-dtyTFKx2xVcO0W8JKaluXIHC9l/MLjHeflBaWjiWNMCHp/TBs9dEjQDbj/VFlHR4omFOKjjmqm1pW1aCAhmPBg==", + "requires": { + "apg-lite": "^1.0.3" + } + }, + "openapi-types": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", + "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" + }, + "openapi-typescript": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-5.3.0.tgz", + "integrity": "sha512-Y1QI1nWQE56nYFGXMAanS9VVHCUKzgLCfAe43vFbuciqm48N02DX9LWSarX18PGF9qKd6EWhmIlCumOXKPzAug==", + "dev": true, + "requires": { + "js-yaml": "^4.1.0", + "mime": "^3.0.0", + "prettier": "^2.6.2", + "tiny-glob": "^0.2.9", + "undici": "^5.0.0", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + } + } + }, + "openid-client": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.0.tgz", + "integrity": "sha512-4GCCGZt1i2kTHpwvaC/sCpTpQqDnBzDzuJcJMbH+y1Q5qI8U8RBvoSh28svarXszZHR5BAMXbJPX1PGPRE3VOA==", + "optional": true, + "requires": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "p-wait-for": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-wait-for/-/p-wait-for-3.2.0.tgz", + "integrity": "sha512-wpgERjNkLrBiFmkMEjuZJEWKKDrNfHCKA1OhyN1wg1FrLkULbviEy6py1AyJUgZ72YWFbZ38FIpnqvVqAlDUwA==", + "requires": { + "p-timeout": "^3.0.0" + } + }, + "package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "requires": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "dependencies": { + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + } + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "requires": { + "mimic-response": "^1.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + } + } + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "requires": { + "json-buffer": "3.0.0" + } + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "requires": { + "lowercase-keys": "^1.0.0" + }, + "dependencies": { + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + } + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "parent-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", + "integrity": "sha512-uo0Z9JJeWzv8BG+tRcapBKNJ0dro9cLyczGzulS6EfeyAdeC9sbojtW6XwvYxJkEne9En+J2XEl4zyglVeIwFg==", + "dev": true, + "requires": { + "callsites": "^3.1.0" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + } + } + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + } + } + }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, + "plimit-lit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.4.1.tgz", + "integrity": "sha512-bK14ePAod0XWhXwjT6XvYfjcQ9PbCUkZXnDCAKRMZTJCaDIV9VFya1S/I+3WSbpdR8uBhCDh8TS4lQ/JQvhNFA==", + "dev": true, + "requires": { + "queue-lit": "^1.4.0" + } + }, + "pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true + }, + "prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "project-name-generator": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/project-name-generator/-/project-name-generator-2.1.9.tgz", + "integrity": "sha512-QmLHqz2C4VHmAyDEAFlVfnuWAHr4vwZhK2bbm4IrwuHNzNKOdG9b4U+NmQbsm1uOoV4kGWv7+FVLsu7Bb/ieYQ==", + "dev": true, + "requires": { + "commander": "^6.1.0", + "lodash": "^4.17.20" + }, + "dependencies": { + "commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, + "property-expr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "proxyquire": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz", + "integrity": "sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg==", + "dev": true, + "requires": { + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.1", + "resolve": "^1.11.1" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "requires": { + "escape-goat": "^2.0.0" + } + }, + "pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true + }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "dev": true + }, + "qs": { + "version": "6.9.6", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", + "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "queue-lit": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.4.0.tgz", + "integrity": "sha512-l1+4YHm4vHWpCnvTg8JMsnPETmPvLGWhqjvNOc8TSbqscGplHVSWXOxybA3vYeMNNIR9Z1PQt85U+S3wFJX2uQ==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==" + }, + "ramda-adjunct": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", + "requires": {} + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", + "integrity": "sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ==", + "requires": { + "bytes": "3.1.1", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + } + } + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "requires": { + "rc": "^1.2.8" + } + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "resolve": { + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", + "requires": { + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-global": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-global/-/resolve-global-1.0.0.tgz", + "integrity": "sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==", + "dev": true, + "requires": { + "global-dirs": "^0.1.1" + } + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rfc4648": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.5.1.tgz", + "integrity": "sha512-60e/YWs2/D3MV1ErdjhJHcmlgnyLUiG4X/14dgsfm9/zmCWLN16xI6YqJYSCd/OANM7bUNzJqPY5B8/02S9Ibw==" + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "roarr": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-7.8.0.tgz", + "integrity": "sha512-BKPYQkqzoOsKeaJEkx0WWW4hh/SSxOyRqhjnL6SSypMHV3ZmINkKyGIDRvinB4AMXfKBUGiy1J7qtl6teIC5tQ==", + "requires": { + "boolean": "^3.1.4", + "fast-json-stringify": "^2.7.10", + "fast-printf": "^1.6.9", + "globalthis": "^1.0.2", + "is-circular": "^1.0.2", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "requires": { + "tslib": "^2.1.0" + }, + "dependencies": { + "tslib": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", + "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", + "dev": true + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" + }, + "semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", + "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "serve-static": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", + "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "shelljs": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "short-unique-id": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", + "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "simple-git": { + "version": "3.27.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", + "integrity": "sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==", + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.5" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "simple-websocket": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/simple-websocket/-/simple-websocket-9.1.0.tgz", + "integrity": "sha512-8MJPnjRN6A8UCp1I+H/dSFyjwJhp6wta4hsVRhjf8w9qBHRzxYt14RaOcjvQnhD1N4yKOddEjflwMnQM4VtXjQ==", + "dev": true, + "requires": { + "debug": "^4.3.1", + "queue-microtask": "^1.2.2", + "randombytes": "^2.1.0", + "readable-stream": "^3.6.0", + "ws": "^7.4.2" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "socket.io": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.2.tgz", + "integrity": "sha512-6fCnk4ARMPZN448+SQcnn1u8OHUC72puJcNtSgg2xS34Cu7br1gQ09YKkO1PFfDn/wyUE9ZgMAwosJed003+NQ==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "socket.io-client": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.2.tgz", + "integrity": "sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.2.1", + "socket.io-parser": "~4.2.0" + } + }, + "socket.io-parser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz", + "integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dev": true, + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "standard-version": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", + "integrity": "sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "conventional-changelog": "3.1.25", + "conventional-changelog-config-spec": "2.1.0", + "conventional-changelog-conventionalcommits": "4.6.3", + "conventional-recommended-bump": "6.1.0", + "detect-indent": "^6.0.0", + "detect-newline": "^3.1.0", + "dotgitignore": "^2.1.0", + "figures": "^3.1.0", + "find-up": "^5.0.0", + "git-semver-tags": "^4.0.0", + "semver": "^7.1.1", + "stringify-package": "^1.0.1", + "yargs": "^16.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "static-eval": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/static-eval/-/static-eval-2.0.2.tgz", + "integrity": "sha512-N/D219Hcr2bPjLxPiV+TQE++Tsmrady7TqAJugLy7Xk1EumfDWS/f5dtBbkRCGE7wKKXuYockQoj8Rm2/pVKyg==", + "requires": { + "escodegen": "^1.8.1" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "steno": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/steno/-/steno-0.4.4.tgz", + "integrity": "sha1-BxEFvfwobmYVwEA8J+nXtdy4Vcs=", + "requires": { + "graceful-fs": "^4.1.3" + } + }, + "stream-buffers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", + "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==" + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "string-argv": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", + "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "dev": true + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-similarity": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-4.0.4.tgz", + "integrity": "sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==" + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + } + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + } + } + }, + "string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "requires": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "dependencies": { + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", + "dev": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stringify-package/-/stringify-package-1.0.1.tgz", + "integrity": "sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "superagent": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "dev": true, + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "supertest": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-4.0.2.tgz", + "integrity": "sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==", + "dev": true, + "requires": { + "methods": "^1.1.2", + "superagent": "^3.8.3" + } + }, + "supports-color": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.2.2.tgz", + "integrity": "sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==", + "dev": true + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "swagger-client": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.32.1.tgz", + "integrity": "sha512-vXRjuiUU8QbyniRwv/cOcv2glJS3eLrj3JRmge+R/Kwb+xH9t9SzWAyoalhpAkOlm+NEqpJe9wmbOJbYGR74+g==", + "requires": { + "@babel/runtime-corejs3": "^7.22.15", + "@scarf/scarf": "=1.4.0", + "@swagger-api/apidom-core": ">=1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-error": ">=1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-json-pointer": ">=1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-ns-openapi-3-1": ">=1.0.0-beta.3 <1.0.0-rc.0", + "@swagger-api/apidom-reference": ">=1.0.0-beta.3 <1.0.0-rc.0", + "cookie": "~0.7.2", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "neotraverse": "=0.6.18", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^1.5.1", + "openapi-server-url-templating": "^1.0.0", + "ramda": "^0.30.1", + "ramda-adjunct": "^5.0.0" + }, + "dependencies": { + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + } + } + }, + "swagger-node-codegen": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/swagger-node-codegen/-/swagger-node-codegen-1.6.3.tgz", + "integrity": "sha512-ot3UpJR3iD7hHQf7LgzxL7pUhPg4GDa4uQh+8Pecdrfb9k3UpCkJxdR0majoOKJzyMy17q1EoA72dI32tRa/fQ==", + "dev": true, + "requires": { + "commander": "^2.12.2", + "fs.extra": "1.3.2", + "handlebars": "^4.1.2", + "js-yaml": "^3.13.1", + "json-schema-ref-parser": "^4.0.4", + "lodash": "^4.17.11", + "project-name-generator": "^2.1.6", + "word-wrap": "1.1.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "word-wrap": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.1.0.tgz", + "integrity": "sha1-NWFT1h0QYQ1gB4XF1wEojgrnZKY=", + "dev": true + } + } + }, + "swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=", + "dev": true + }, + "swagger-ui-dist": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-4.5.0.tgz", + "integrity": "sha512-s00bemwjowAeGGCPxj4BmZrTbeKc9ig/99UEuJUVsaDXovIALD5/Hj0tmDCBGT3tgZQ9O7LrBdPmUlyhcudsLQ==" + }, + "swagger-ui-express": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.3.0.tgz", + "integrity": "sha512-jN46SEEe9EoXa3ZgZoKgnSF6z0w3tnM1yqhO4Y+Q4iZVc8JOQB960EZpIAz6rNROrDApVDwcMHR0mhlnc/5Omw==", + "requires": { + "swagger-ui-dist": ">=4.1.3" + } + }, + "synckit": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.4.tgz", + "integrity": "sha512-Dn2ZkzMdSX827QbowGbU/4yjWuvNaCoScLLoMo/yKbu+P4GBR6cRGKZH27k6a9bRzdqcyd1DE96pQtQ6uNkmyw==", + "dev": true, + "requires": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.4.0" + }, + "dependencies": { + "tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + } + } + }, + "tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true + }, + "tar": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz", + "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==", + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==" + }, + "yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + } + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "text-extensions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.9.0.tgz", + "integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "dev": true, + "requires": { + "readable-stream": "3" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + } + } + } + }, + "tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "requires": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + }, + "dependencies": { + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + } + } + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "toposort": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", + "dev": true + }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "optional": true, + "requires": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "tree-sitter-json": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz", + "integrity": "sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==", + "optional": true, + "requires": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + } + }, + "trim-newlines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", + "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==", + "dev": true + }, + "ts-custom-error": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz", + "integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==" + }, + "ts-essentials": { + "version": "10.0.4", + "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz", + "integrity": "sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==", + "dev": true, + "requires": {} + }, + "ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "requires": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } + }, + "ts-log": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/ts-log/-/ts-log-2.2.4.tgz", + "integrity": "sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==" + }, + "ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==" + }, + "ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, + "ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "requires": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "dependencies": { + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==" + }, + "tsc-alias": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.7.0.tgz", + "integrity": "sha512-n/K6g8S7Ec7Y/A2Z77Ikp2Uv1S1ERtT63ni69XV4W1YPT4rnNmz8ItgIiJYvKfFnKfqcZQ81UPjoKpMTxaC/rg==", + "dev": true, + "requires": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, + "tsconfig-paths": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.1.0.tgz", + "integrity": "sha512-AHx4Euop/dXFC+Vx589alFba8QItjF+8hf8LtmuiCwHyI4rHXQtOOENaM8kvYf5fR0dRChy3wzWIZ9WbB7FWow==", + "dev": true, + "requires": { + "json5": "^2.2.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + } + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "requires": { + "ts-toolbelt": "^9.6.0" + } + }, + "typescript": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "dev": true + }, + "uglify-js": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz", + "integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==", + "dev": true, + "optional": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "undici": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.1.1.tgz", + "integrity": "sha512-CmK9JzLSMGx+2msOao8LhkKn3J7eKo2M50v0KZQ2XbiHcGqLS1HiIj01ceIm3jbUYlspw/FTSb6nMdSNyvVyaQ==", + "dev": true + }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + } + } + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "requires": { + "crypto-random-string": "^2.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", + "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "dev": true, + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, + "update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "requires": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + } + }, + "upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", + "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "requires": { + "prepend-http": "^2.0.0" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "dependencies": { + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + } + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz", + "integrity": "sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==", + "dev": true + }, + "vscode-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.3.tgz", + "integrity": "sha512-EcswR2S8bpR7fD0YPeS7r2xXExrScVMxg4MedACaWHEtx9ftCF/qHG1xGkolzTPcEmjTavCQgbVzHUIdTMzFGA==", + "dev": true + }, + "walk": { + "version": "2.3.15", + "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.15.tgz", + "integrity": "sha512-4eRTBZljBfIISK1Vnt69Gvr2w/wc3U6Vtrw7qiN5iqYJPH7LElcYh/iU4XWhdCy2dZqv1ToMyYlybDylfG/5Vg==", + "dev": true, + "requires": { + "foreachasync": "^3.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "watch": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.19.2.tgz", + "integrity": "sha1-MXr+3kg81inYEMTljvD/jEH8ahg=", + "dev": true, + "requires": { + "exec-sh": "^0.2.0", + "minimist": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==" + }, + "web-tree-sitter": { + "version": "0.24.3", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.3.tgz", + "integrity": "sha512-uR9YNewr1S2EzPKE+y39nAwaTyobBaZRG/IsfkB/OT4v0lXtNj5WjtHKgn2h7eOYUWIZh5rK9Px7tI6S9CRKdA==", + "optional": true + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", + "dev": true + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dev": true, + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "requires": { + "string-width": "^4.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.5.7", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "requires": {} + }, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yaml": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz", + "integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==" + }, + "yaml-ast-parser": { + "version": "0.0.43", + "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", + "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", + "dev": true + }, + "yargs": { + "version": "17.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", + "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "dependencies": { + "yargs-parser": { + "version": "21.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", + "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "dev": true + } + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + }, + "yup": { + "version": "0.32.11", + "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", + "integrity": "sha512-Z2Fe1bn+eLstG8DRR6FTavGD+MeAwyfmouhHsIUgaADz8jvFKbO/fXc2trJKZg+5EBjh4gGm3iU/t3onKlXHIg==", + "requires": { + "@babel/runtime": "^7.15.4", + "@types/lodash": "^4.14.175", + "lodash": "^4.17.21", + "lodash-es": "^4.17.21", + "nanoclone": "^0.2.1", + "property-expr": "^2.0.4", + "toposort": "^2.0.2" + } + } } } diff --git a/package.json b/package.json index ae96d477a..b2e2a9498 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@types/express": "4.17.13", "@types/fs-extra": "^9.0.13", "@types/jest": "^29.5.14", + "@types/jsonpath": "^0.2.4", "@types/lodash": "4.14.182", "@types/lowdb": "1.0.11", "@types/node": "^16.18.125", diff --git a/src/api/apps/{teamId}/{appId}.ts b/src/api/apps/{teamId}/{appId}.ts index c4d70736b..a72377c25 100644 --- a/src/api/apps/{teamId}/{appId}.ts +++ b/src/api/apps/{teamId}/{appId}.ts @@ -4,7 +4,7 @@ import { App, OpenApiRequestExt } from 'src/otomi-models' export default function (): OperationHandlerArray { const get: Operation = [ ({ otomi, params: { teamId, appId } }: OpenApiRequestExt, res): void => { - res.json(otomi.getApp(teamId, appId)) + res.json(otomi.getTeamApp(teamId, appId)) }, ] const put: Operation = [ diff --git a/src/app.ts b/src/app.ts index ade0a7c64..70a47d9b2 100644 --- a/src/app.ts +++ b/src/app.ts @@ -66,7 +66,6 @@ const checkAgainstGitea = async () => { debug('Local values differentiate from Git repository, retrieving latest values') await otomiStack.git.pull() // inflate new db - await otomiStack.initRepo() await otomiStack.loadValues() const sha = await otomiStack.git.getCommitSha() const msg: DbMessage = { state: 'clean', editor: 'system', sha, reason: 'conflict' } diff --git a/src/git.ts b/src/git.ts index 146ba3e53..2a0eb870b 100644 --- a/src/git.ts +++ b/src/git.ts @@ -14,6 +14,8 @@ import { GitPullError, HttpError, ValidationError } from './error' import { DbMessage, getIo } from './middleware' import { Core } from './otomi-models' import { removeBlankAttributes } from './utils' +import { FileMap, getFilePath, renderManifest, renderManifestForSecrets } from './repo' +import jsonpath from 'jsonpath' const debug = Debug('otomi:repo') @@ -199,11 +201,30 @@ export class Git { return merge(data, secretData) as Core } - async saveConfig( - dataPath: string, + async saveConfig(config: Record, fileMap: FileMap): Promise> { + const jsonPathsValuesPublic = jsonpath.nodes(config, fileMap.jsonPathExpression) + await Promise.all( + jsonPathsValuesPublic.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, '') + const manifest = renderManifest(fileMap, nodePath, nodeValue) + await this.writeFile(filePath, manifest) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e + } + }), + ) + } + + async saveConfigWithSecrets( inSecretRelativeFilePath: string, config: Record, secretJsonPaths: string[], + fileMap: FileMap, ): Promise> { const secretData = {} const plainData = cloneDeep(config) @@ -223,8 +244,26 @@ export class Git { if (!secretExists) secretDataRelativePath = inSecretRelativeFilePath } - await this.writeFile(secretDataRelativePath, secretData) - await this.writeFile(dataPath, plainData) + const jsonPathsValuesPublic = jsonpath.nodes(plainData, fileMap.jsonPathExpression) + await Promise.all( + jsonPathsValuesPublic.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, '') + const manifest = renderManifest(fileMap, nodePath, nodeValue) + await this.writeFile(filePath, manifest) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e + } + }), + ) + if (secretData && Object.keys(secretData).length > 0) { + const secretManifest = renderManifestForSecrets(fileMap, secretData) + await this.writeFile(secretDataRelativePath, secretManifest) + } } isRootClone(): boolean { diff --git a/src/middleware/authz.ts b/src/middleware/authz.ts index 28ef28b6f..15f7ae405 100644 --- a/src/middleware/authz.ts +++ b/src/middleware/authz.ts @@ -76,9 +76,11 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS const schemaToRepoMap: Record = { Service: 'services', Team: 'teamConfig', + App: 'apps', } const selector = renameKeys(req.params) + console.log(schemaName) const collectionId = schemaToRepoMap[schemaName] if (['create', 'update'].includes(action)) { let dataOrig = get( diff --git a/src/middleware/error.ts b/src/middleware/error.ts index e41618758..273aaa1be 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -4,7 +4,6 @@ import { Response } from 'express' import { HttpError, OtomiError } from 'src/error' import { OpenApiRequest } from 'src/otomi-models' import { cleanEnv } from 'src/validators' -import { cleanSession } from './session' const env = cleanEnv({}) @@ -28,6 +27,6 @@ export function errorMiddleware(e, req: OpenApiRequest, res: Response, next): vo msg = `Required property missing! '${requiredProperties}'` } const { otomi } = req as any - if (otomi?.sessionId) cleanSession(otomi.sessionId as string) + // if (otomi?.sessionId) cleanSession(otomi.sessionId as string) res.status(code).json({ error: msg }) } diff --git a/src/middleware/jwt.ts b/src/middleware/jwt.ts index 1c142a9bc..e7452de81 100644 --- a/src/middleware/jwt.ts +++ b/src/middleware/jwt.ts @@ -37,7 +37,7 @@ export function getUser(user: JWT, otomi: OtomiStack): SessionUser { const teamId = group.substring(5) if (group.substring(0, 5) === 'team-' && !sessionUser.teams.includes(teamId)) { // we might be assigned team-* without that team yet existing in the values, so ignore those - const existing = otomi.repoService.getTeamConfigService(teamId).getSettings() + const existing = otomi.repoService.getTeamConfig(teamId) if (existing) sessionUser.teams.push(teamId) } }) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index d25f966e8..bceb1049b 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -42,7 +42,6 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis if (!sessions[sessionId]) { debug(`Creating session ${sessionId} for user ${editor}`) sessions[sessionId] = new OtomiStack(editor, sessionId) - await sessions[sessionId].initRepo(readOnlyStack.repoService) // init repo without inflating db from files as its slow and we just need a copy of the db await sessions[sessionId].initGit(true) sessions[sessionId].repoService.setRepo(cloneDeep(readOnlyStack.repoService.getRepo())) diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 244d9366a..9b15645cd 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -2,6 +2,7 @@ import { Request } from 'express' import { JSONSchema4 } from 'json-schema' import { components, external, operations, paths } from 'src/generated-schema' import OtomiStack from 'src/otomi-stack' + export type App = components['schemas']['App'] export type AppList = components['schemas']['AppList'] export type Backup = components['schemas']['Backup'] @@ -149,7 +150,7 @@ export interface Core { } export interface Repo { - apps: Record + apps: App[] alerts: Alerts cluster: Cluster databases: Record @@ -167,7 +168,7 @@ export interface Repo { } export interface TeamConfig { - apps: Record + apps: App[] backups: Backup[] builds: Build[] netpols: Netpol[] diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index df5469da5..f3e38bc88 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -7,7 +7,7 @@ import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' import { emptyDir, pathExists, unlink } from 'fs-extra' import { readdir, readFile, writeFile } from 'fs/promises' import { generate as generatePassword } from 'generate-password' -import { cloneDeep, filter, get, isArray, isEmpty, map, omit, pick, set, unset } from 'lodash' +import { cloneDeep, filter, get, isArray, isEmpty, map, mapValues, omit, pick, set, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' import { cleanAllSessions, cleanSession, DbMessage, getIo, getSessionStack } from 'src/middleware' @@ -82,16 +82,15 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' -import { loadValues } from './repo' +import { getFileMaps, getFilePath, loadValues, renderManifestForSecrets } from './repo' import { RepoService } from './services/RepoService' +import jsonpath from 'jsonpath' interface ExcludedApp extends App { managed: boolean } const debug = Debug('otomi:otomi-stack') -const secretTransferProps = ['type', 'ca', 'crt', 'key', 'entries', 'dockerconfig'] - const env = cleanEnv({ CUSTOM_ROOT_CA, DEFAULT_PLATFORM_ADMIN_EMAIL, @@ -232,19 +231,38 @@ export default class OtomiStack { } } + transformApps(appsObj: Record): App[] { + if (!appsObj || typeof appsObj !== 'object') return [] + + return Object.entries(appsObj).map(([appId, appData]) => ({ + id: appId, + enabled: appData.enabled ?? false, + values: omit(appData, ['enabled']), // Remove `enabled` from values + rawValues: {}, + })) + } + async initRepo(repoService?: RepoService): Promise { if (repoService) { this.repoService = repoService return } else { - const repo = (await loadValues(this.getRepoPath())) as Repo + // We need to map the app values so it adheres the App interface + const rawRepo = await loadValues(this.getRepoPath()) + + rawRepo.apps = this.transformApps(rawRepo.apps) + rawRepo.teamConfig = mapValues(rawRepo.teamConfig, (teamConfig) => ({ + ...teamConfig, + apps: this.transformApps(teamConfig.apps), + })) + + const repo = rawRepo as Repo this.repoService = new RepoService(repo) } } async initGit(skipDbInflation = false): Promise { await this.init() - await this.initRepo() // every editor gets their own folder to detect conflicts upon deploy const path = this.getRepoPath() const branch = env.GIT_BRANCH @@ -253,7 +271,8 @@ export default class OtomiStack { try { this.git = await getRepo(path, url, env.GIT_USER, env.GIT_EMAIL, env.GIT_PASSWORD, branch) await this.git.pull() - if (await this.git.fileExists('env/cluster.yaml')) break + //TODO fetch this url from the repo + if (await this.git.fileExists('env/settings/cluster.yaml')) break debug(`Values are not present at ${url}:${branch}`) } catch (e) { debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) @@ -264,7 +283,7 @@ export default class OtomiStack { await new Promise((resolve) => setTimeout(resolve, timeoutMs)) } // branches get a copy of the "main" branch db, so we don't need to inflate - if (!skipDbInflation) await this.loadValues() + await this.loadValues() debug(`Values are loaded for ${this.editor} in ${this.sessionId}`) } @@ -385,7 +404,7 @@ export default class OtomiStack { const content = await this.git.loadConfig('env/apps/ingress-nginx.yaml', 'env/apps/secrets.ingress-nginx.yaml') const values = content?.apps?.['ingress-nginx'] ?? {} const teamId = 'admin' - this.repoService.getTeamConfigService(teamId).createApp(id, { enabled: true, values, rawValues: {}, teamId }) + this.repoService.getTeamConfigService(teamId).createApp({ enabled: true, values, rawValues: {}, id }) debug(`Ingress app loaded for ${id}`) } catch (error) { debug(`Failed to load ingress apps for ${id}:`) @@ -476,30 +495,40 @@ export default class OtomiStack { return apps } - getApp(teamId: string, id: string): App | ExcludedApp { - const app = this.repoService.getTeamConfigService(teamId).getApp(id) as App + getTeamApp(teamId: string, id: string): App | ExcludedApp { + const app = this.getApp(id) this.filterExcludedApp(app) if (teamId === 'admin') return app - const adminApp = this.repoService.getTeamConfigService(teamId).getApp(id) as App + const adminApp = this.repoService.getTeamConfigService(teamId).getApp(id) return { ...cloneDeep(app), enabled: adminApp.enabled } } + getApp(name: string): App { + return this.repoService.getApp(name) + } + getApps(teamId: string, picks?: string[]): Array { - const apps = this.repoService.getTeamConfigService(teamId).getApps() as Array + const appList = this.getAppList() + const apps = this.repoService.getApps().filter((app) => appList.includes(app.id)) const providerSpecificApps = this.filterExcludedApp(apps) as App[] if (teamId === 'admin') return providerSpecificApps - let teamApps = providerSpecificApps.map((app: App) => { - const adminApp = this.repoService.getTeamConfigService('admin').getApp(app.id) as App - return { ...cloneDeep(app), enabled: adminApp.enabled } - }) + // If not team admin load available teamApps + const core = this.getCore() + let teamApps = providerSpecificApps + .map((app: App) => { + const isShared = !!core.adminApps.find((a) => a.name === app.id)?.isShared + const inTeamApps = !!core.teamApps.find((a) => a.name === app.id) + if (isShared || inTeamApps) return app + }) + .filter((app): app is App => app !== undefined) // Ensures no `undefined` elements if (!picks) return teamApps if (picks.includes('enabled')) { - const adminApps = this.repoService.getTeamConfigService('admin').getApps() as Array + const adminApps = this.repoService.getApps() teamApps = adminApps.map((adminApp) => { const teamApp = teamApps.find((app) => app.id === adminApp.id) @@ -511,15 +540,15 @@ export default class OtomiStack { } async editApp(teamId: string, id: string, data: App): Promise { - let app: App = this.repoService.getTeamConfigService(teamId).getApp(id) + let app: App = this.repoService.getApp(id) // Shallow merge, so only first level attributes can be replaced (values, rawValues, etc.) app = { ...app, ...data } - this.repoService.getTeamConfigService(teamId).updateApp(id, app) + this.repoService.updateApp(id, app) const secretPaths = this.getSecretPaths() // also save admin apps await this.saveAdminApps(secretPaths) await this.doDeployment(['apps']) - return this.repoService.getTeamConfigService(teamId).getApp(id) as App + return this.repoService.getApp(id) } canToggleApp(id: string): boolean { @@ -529,11 +558,15 @@ export default class OtomiStack { async toggleApps(teamId: string, ids: string[], enabled: boolean): Promise { ids.map((id) => { - // we might be given a dep that is only relevant to core, or - // which is essential, so skip it - const orig = this.repoService.getTeamConfigService(teamId).getApp(id) as App - if (orig && this.canToggleApp(id)) this.repoService.getTeamConfigService(teamId).updateApp(id, { enabled }) + const orig = this.repoService.getApp(id) + console.log(JSON.stringify(orig)) + console.log(JSON.stringify(this.canToggleApp(id))) + if (orig && this.canToggleApp(id)) { + const app = this.repoService.updateApp(id, { enabled }) + console.log(JSON.stringify(app)) + } }) + const secretPaths = this.getSecretPaths() // also save admin apps await this.saveAdminApps(secretPaths) @@ -566,7 +599,7 @@ export default class OtomiStack { // we do not want to send enabled flag to the input forms delete values.enabled const teamId = 'admin' - this.repoService.getTeamConfigService(teamId).createApp(appInstanceId, { enabled, values, rawValues, teamId }) + this.repoService.getTeamConfigService(teamId).createApp({ enabled, values, rawValues, id: appId }) } async loadApps(): Promise { @@ -619,7 +652,7 @@ export default class OtomiStack { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) // Admin apps are loaded by loadApps function - if (id !== 'admin' && (isShared || inTeamApps)) this.repoService.getTeamConfigService(id).createApp(appId, {}) + if (id !== 'admin' && (isShared || inTeamApps)) this.repoService.getTeamConfigService(id).createApp({ id: appId }) }) if (!data.id) { @@ -763,7 +796,7 @@ export default class OtomiStack { let existingUsersEmail = this.repoService.getUsersEmail() if (!env.isDev) { const { otomi, cluster } = this.getSettings(['otomi', 'cluster']) - const keycloak = this.getApp('admin', 'keycloak') + const keycloak = this.getApp('keycloak') const keycloakBaseUrl = `https://keycloak.${cluster?.domainSuffix}` const realm = 'otomi' const username = keycloak?.values?.adminUsername as string @@ -1617,11 +1650,12 @@ export default class OtomiStack { async loadValues(): Promise>>>> { debug('Loading values') await this.git.initSops() - await this.loadCluster() - await this.loadSettings() - await this.loadUsers() - await this.loadTeams() - await this.loadApps() + await this.initRepo() + // await this.loadCluster() + // await this.loadSettings() + // await this.loadUsers() + // await this.loadTeams() + // await this.loadApps() this.isLoaded = true } @@ -1847,15 +1881,6 @@ export default class OtomiStack { }) } - async saveCluster(secretPaths?: string[]): Promise { - await this.git.saveConfig( - 'env/cluster.yaml', - 'env/secrets.cluster.yaml', - this.getSettings(['cluster']), - secretPaths ?? this.getSecretPaths(), - ) - } - async saveAdminApps(secretPaths?: string[]): Promise { await Promise.all( this.getApps('admin').map(async (app) => { @@ -1869,11 +1894,12 @@ export default class OtomiStack { if (this.canToggleApp(id)) apps[id].enabled = !!enabled else delete apps[id].enabled - await this.git.saveConfig( - `env/apps/${id}.yaml`, + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplApp')! + await this.git.saveConfigWithSecrets( `env/apps/secrets.${id}.yaml`, { apps }, secretPaths ?? this.getSecretPaths(), + fileMap, ) }), ) @@ -1882,34 +1908,40 @@ export default class OtomiStack { async saveSettings(secretPaths?: string[]): Promise { const settings = cloneDeep(this.getSettings()) as Record> settings.otomi.nodeSelector = arrayToObject(settings.otomi.nodeSelector as []) - await this.git.saveConfig( - 'env/settings.yaml', + //TODO this one needs to be done for all settings + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplSmtp')! + await this.git.saveConfigWithSecrets( `env/secrets.settings.yaml`, omit(settings, ['cluster']), secretPaths ?? this.getSecretPaths(), + fileMap, ) } async saveUsers(): Promise { const users = this.repoService.getUsers() - const relativePath = `env/secrets.users.yaml` - const { secretFilePostfix } = this.git - let secretRelativePath = `${relativePath}${secretFilePostfix}` - if (secretFilePostfix) { - const secretExists = await this.git.fileExists(relativePath) - if (!secretExists) secretRelativePath = relativePath - } - const outData: Record = set({}, `users`, users) debug(`Saving users`) - await this.git.writeFile(secretRelativePath, outData, false) - if (users.length === 0) { - await this.git.removeFile(relativePath) - await this.git.removeFile(secretRelativePath) - } + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplUser')! + const jsonPathsvaluesSecrets = jsonpath.nodes(users, fileMap.jsonPathExpression) + //TODO check what to do when we delete a user + await Promise.all( + jsonPathsvaluesSecrets.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') + const manifest = renderManifestForSecrets(fileMap, nodeValue) + await this.git.writeFile(filePath, manifest, false) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e + } + }), + ) } async saveTeams(secretPaths?: string[]): Promise { - const filePath = 'env/teams.yaml' const secretFilePath = `env/secrets.teams.yaml` const teamValues = {} const teams = this.getTeams() @@ -1922,7 +1954,8 @@ export default class OtomiStack { }), ) const values = set({}, 'teamConfig', teamValues) - await this.git.saveConfig(filePath, secretFilePath, values, secretPaths ?? this.getSecretPaths()) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamSettingSet')! + await this.git.saveConfigWithSecrets(secretFilePath, values, secretPaths ?? this.getSecretPaths(), fileMap) } async saveTeamSealedSecrets(teamId: string, data: any, id: string): Promise { @@ -1933,35 +1966,23 @@ export default class OtomiStack { async saveTeamBackups(teamId: string): Promise { const backups = this.repoService.getTeamConfigService(teamId).getBackups() - const cleaneBackups: Array> = backups.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamBackupsFilePath(teamId) - const outData: Record = set({}, getTeamBackupsJsonPath(teamId), cleaneBackups) debug(`Saving backups of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamBackup')! + await this.git.saveConfig(backups, fileMap) } async saveTeamNetpols(teamId: string): Promise { const netpols = this.repoService.getTeamConfigService(teamId).getNetpols() - const cleaneNetpols: Array> = netpols.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamNetpolsFilePath(teamId) - const outData: Record = set({}, getTeamNetpolsJsonPath(teamId), cleaneNetpols) debug(`Saving network policies of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamNetworkControl')! + await this.git.saveConfig(netpols, fileMap) } async saveTeamWorkloads(teamId: string): Promise { const workloads = this.repoService.getTeamConfigService(teamId).getWorkloads() - const cleaneWorkloads: Array> = workloads.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamWorkloadsFilePath(teamId) - const outData: Record = set({}, getTeamWorkloadsJsonPath(teamId), cleaneWorkloads) debug(`Saving workloads of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamWorkload')! + await this.git.saveConfig(workloads, fileMap) await Promise.all( workloads.map((workload) => { this.saveWorkloadValues(teamId, workload) @@ -1971,13 +1992,9 @@ export default class OtomiStack { async saveTeamProjects(teamId: string): Promise { const projects = this.repoService.getTeamConfigService(teamId).getProjects() - const cleaneProjects: Array> = projects.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamProjectsFilePath(teamId) - const outData: Record = set({}, getTeamProjectsJsonPath(teamId), cleaneProjects) debug(`Saving projects of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamProject')! + await this.git.saveConfig(projects, fileMap) } async saveTeamCoderepos(teamId: string): Promise { @@ -1993,21 +2010,16 @@ export default class OtomiStack { async saveTeamBuilds(teamId: string): Promise { const builds = this.repoService.getTeamConfigService(teamId).getBuilds() - const cleaneBuilds: Array> = builds.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamBuildsFilePath(teamId) - const outData: Record = set({}, getTeamBuildsJsonPath(teamId), cleaneBuilds) debug(`Saving builds of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamBuild')! + await this.git.saveConfig(builds, fileMap) } async saveTeamPolicies(teamId: string): Promise { const policies = this.getTeamPolicies(teamId) - const relativePath = getTeamPoliciesFilePath(teamId) - const outData: Record = set({}, getTeamPoliciesJsonPath(teamId), policies) debug(`Saving policies of team: ${teamId}`) - await this.git.writeFile(relativePath, outData) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamPolicy')! + await this.git.saveConfig(policies, fileMap) } async saveWorkloadValues(teamId: string, workload: Workload): Promise { @@ -2015,23 +2027,23 @@ export default class OtomiStack { const data = this.getWorkloadValues(teamId, workload.id!) const outData = omit(data, ['id', 'teamId', 'name']) as Record outData.values = stringifyYaml(data.values, undefined, 4) - const path = getTeamWorkloadValuesFilePath(teamId, workload.name) - - await this.git.writeFile(path, outData, false) + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamWorkloadValues')! + await this.git.saveConfig(outData, fileMap) } async saveTeamServices(teamId: string): Promise { const services = this.repoService.getTeamConfigService(teamId).getServices() + //TODO do we need this const data = {} const values: any[] = [] services.forEach((service) => { const value = this.convertDbServiceToValues(service) values.push(value) }) - set(data, getTeamServicesJsonPath(teamId), values) - const filePath = getTeamServicesFilePath(teamId) - await this.git.writeFile(filePath, data) + + const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamService')! + await this.git.saveConfig(services, fileMap) } async loadTeam(inTeam: Team): Promise { diff --git a/src/playground.ts b/src/playground.ts index dcad87973..7799644c5 100644 --- a/src/playground.ts +++ b/src/playground.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node --nolazy -r ts-node/register -r tsconfig-paths/register -import { loadValues } from './repo' +import { getFileMaps, getFilePath, loadValues } from './repo' import { Repo } from './otomi-models' async function play() { @@ -10,8 +10,17 @@ async function play() { const allSpecs = await loadValues(envDir) const repo = allSpecs as Repo + + const build = repo.teamConfig['demo'].builds[0] + const jsonPath = ['$', 'teamConfig', 'demo'] + const filePath = getFilePath( + getFileMaps(envDir).find((filemap) => filemap.kind === 'AplTeamBuild')!, + jsonPath, + build, + '', + ) console.log(allSpecs) - console.log(repo) + console.log(filePath) } play() diff --git a/src/repo.ts b/src/repo.ts index b8f752003..ab0a8a81c 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -1,14 +1,23 @@ +// IMPORTANT DON'T CHANGE THIS FILE. It's a copy of the one in apl-core +import { rmSync } from 'fs' +import { pathExists } from 'fs-extra' +import { rm, writeFile } from 'fs/promises' import { globSync } from 'glob' -import { get, merge, set } from 'lodash' -import cloneDeep from 'lodash/cloneDeep' -import { loadYaml } from './utils' +import jsonpath from 'jsonpath' +import { cloneDeep, get, merge, set } from 'lodash' import path from 'path' +import { getDirNames, loadYaml, objectToYaml } from './utils' + +export async function getTeamNames(envDir: string): Promise> { + const teamsDir = path.join(envDir, 'env', 'teams') + return await getDirNames(teamsDir, { skipHidden: true }) +} export interface FileMap { envDir: string kind: - | 'AplCoreApp' - | 'AplAlerts' + | 'AplApp' + | 'AplAlertSet' | 'AplCluster' | 'AplDatabase' | 'AplDns' @@ -16,21 +25,22 @@ export interface FileMap { | 'AplObjectStorage' | 'AplKms' | 'AplIdentityProvider' - | 'AplCapabilities' + | 'AplCapabilitySet' | 'AplSmtp' - | 'AplBackups' + | 'AplBackupCollection' | 'AplUser' | 'AplTeamBuild' | 'AplTeamPolicy' - | 'AplTeamSettings' + | 'AplTeamSettingSet' | 'AplTeamNetworkControl' | 'AplTeamProject' | 'AplTeamBackup' | 'AplTeamSecret' | 'AplTeamService' | 'AplTeamWorkload' - | 'AplTeamTools' - | 'AplVersions' + | 'AplTeamWorkloadValues' + | 'AplTeamTool' + | 'AplVersion' jsonPathExpression: string pathGlob: string processAs: 'arrayItem' | 'mapItem' @@ -38,10 +48,66 @@ export interface FileMap { resourceDir: string } -export const getFileMaps = (envDir: string): Array => { +export function getResourceFileName(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { + let fileName = 'unknown' + if (fileMap.resourceGroup === 'team') { + if (fileMap.processAs === 'arrayItem') { + fileName = data.name || data.id || fileName + } else { + fileName = jsonPath[jsonPath.length - 1].toString() + } + } else { + if (fileMap.processAs === 'arrayItem') { + fileName = data.name || data.id || fileName + } else { + fileName = jsonPath[jsonPath.length - 1].toString() + } + } + return fileName +} + +export function getTeamNameFromJsonPath(jsonPath: jsonpath.PathComponent[]): string { + return jsonPath[2].toString() +} + +export function getResourceName(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { + let resourceName = 'unknown' + if (fileMap.processAs === 'arrayItem') { + resourceName = data.name || data.id || resourceName + return resourceName + } + + if (fileMap.resourceGroup === 'team') { + resourceName = getTeamNameFromJsonPath(jsonPath) + return resourceName + } else { + resourceName = jsonPath[jsonPath.length - 1].toString() + return resourceName + } +} + +export function getFilePath( + fileMap: FileMap, + jsonPath: jsonpath.PathComponent[], + data: Record, + fileNamePrefix: string, +) { + let filePath = '' + const resourceName = getResourceFileName(fileMap, jsonPath, data) + if (fileMap.resourceGroup === 'team') { + const teamName = getTeamNameFromJsonPath(jsonPath) + filePath = `${fileMap.envDir}/env/teams/${teamName}/${fileMap.resourceDir}/${fileNamePrefix}${resourceName}.yaml` + } else { + filePath = `${fileMap.envDir}/env/${fileMap.resourceDir}/${fileNamePrefix}${resourceName}.yaml` + } + // normalize paths like /ab/c/./test/yaml + return path.normalize(filePath) +} + +export function getFileMaps(envDir: string): Array { return [ { - kind: 'AplCoreApp', + kind: 'AplApp', envDir, jsonPathExpression: '$.apps.*', pathGlob: `${envDir}/env/apps/*.{yaml,yaml.dec}`, @@ -51,7 +117,7 @@ export const getFileMaps = (envDir: string): Array => { }, { envDir, - kind: 'AplAlerts', + kind: 'AplAlertSet', jsonPathExpression: '$.alerts', pathGlob: `${envDir}/env/settings/*alerts.{yaml,yaml.dec}`, processAs: 'mapItem', @@ -122,7 +188,7 @@ export const getFileMaps = (envDir: string): Array => { resourceDir: 'settings', }, { - kind: 'AplCapabilities', + kind: 'AplCapabilitySet', envDir, jsonPathExpression: '$.otomi', pathGlob: `${envDir}/env/settings/*otomi.{yaml,yaml.dec}`, @@ -131,7 +197,7 @@ export const getFileMaps = (envDir: string): Array => { resourceDir: 'settings', }, { - kind: 'AplBackups', + kind: 'AplBackupCollection', envDir, jsonPathExpression: '$.platformBackups', pathGlob: `${envDir}/env/settings/*platformBackups.{yaml,yaml.dec}`, @@ -158,7 +224,7 @@ export const getFileMaps = (envDir: string): Array => { resourceDir: 'users', }, { - kind: 'AplVersions', + kind: 'AplVersion', envDir, jsonPathExpression: '$.versions', pathGlob: `${envDir}/env/settings/versions.yaml`, @@ -184,6 +250,15 @@ export const getFileMaps = (envDir: string): Array => { resourceGroup: 'team', resourceDir: 'workloads', }, + { + kind: 'AplTeamWorkloadValues', + envDir, + jsonPathExpression: '$.teamConfig.*.workloadValues[*]', + pathGlob: `${envDir}/env/teams/*/workloadValues/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'workloadValues', + }, { kind: 'AplTeamService', envDir, @@ -230,7 +305,7 @@ export const getFileMaps = (envDir: string): Array => { resourceDir: 'netpols', }, { - kind: 'AplTeamSettings', + kind: 'AplTeamSettingSet', envDir, jsonPathExpression: '$.teamConfig.*.settings', pathGlob: `${envDir}/env/teams/*/*settings{.yaml,.yaml.dec}`, @@ -239,7 +314,7 @@ export const getFileMaps = (envDir: string): Array => { resourceDir: '.', }, { - kind: 'AplTeamTools', + kind: 'AplTeamTool', envDir, jsonPathExpression: '$.teamConfig.*.apps', pathGlob: `${envDir}/env/teams/*/*apps{.yaml,.yaml.dec}`, @@ -259,13 +334,108 @@ export const getFileMaps = (envDir: string): Array => { ] } -export const extractTeamDirectory = (filePath: string): string => { +export function hasCorrespondingDecryptedFile(filePath: string, fileList: Array): boolean { + return fileList.includes(`${filePath}.dec`) +} + +export function renderManifest(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { + const manifest = { + kind: fileMap.kind, + metadata: { + name: getResourceName(fileMap, jsonPath, data), + labels: {}, + }, + spec: data, + } + if (fileMap.resourceGroup === 'team') { + manifest.metadata.labels['apl.io/teamId'] = getTeamNameFromJsonPath(jsonPath) + } + + return manifest +} + +export function renderManifestForSecrets(fileMap: FileMap, data: Record) { + return { + kind: fileMap.kind, + spec: data, + } +} + +// INFO not used right now as we still write the files with the old method +// export async function saveResourceGroupToFiles( +// fileMap: FileMap, +// valuesPublic: Record, +// valuesSecrets: Record, +// deps = { writeValuesToFile }, +// ): Promise { +// const jsonPathsValuesPublic = jsonpath.nodes(valuesPublic, fileMap.jsonPathExpression) +// const jsonPathsvaluesSecrets = jsonpath.nodes(valuesSecrets, fileMap.jsonPathExpression) +// +// await Promise.all( +// jsonPathsValuesPublic.map(async (node) => { +// const nodePath = node.path +// const nodeValue = node.value +// try { +// const filePath = getFilePath(fileMap, nodePath, nodeValue, '') +// const manifest = renderManifest(fileMap, nodePath, nodeValue) +// await deps.writeValuesToFile(filePath, manifest) +// } catch (e) { +// console.log(nodePath) +// console.log(fileMap) +// throw e +// } +// }), +// ) +// +// await Promise.all( +// jsonPathsvaluesSecrets.map(async (node) => { +// const nodePath = node.path +// const nodeValue = node.value +// try { +// const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') +// const manifest = renderManifestForSecrets(fileMap, nodeValue) +// await deps.writeValuesToFile(filePath, manifest) +// } catch (e) { +// console.log(nodePath) +// console.log(fileMap) +// throw e +// } +// }), +// ) +// } +// export async function saveValues( +// envDir: string, +// valuesPublic: Record, +// valuesSecrets: Record, +// deps = { saveResourceGroupToFiles }, +// ): Promise { +// const fileMaps = getFileMaps(envDir) +// await Promise.all( +// fileMaps.map(async (fileMap) => { +// await deps.saveResourceGroupToFiles(fileMap, valuesPublic, valuesSecrets) +// }), +// ) +// } + +export async function unsetValuesFile(envDir: string): Promise { + const valuesPath = path.join(envDir, 'values-repo.yaml') + await rm(valuesPath, { force: true }) + return valuesPath +} + +export function unsetValuesFileSync(envDir: string): string { + const valuesPath = path.join(envDir, 'values-repo.yaml') + rmSync(valuesPath, { force: true }) + return valuesPath +} + +export function extractTeamDirectory(filePath: string): string { const match = filePath.match(/\/teams\/([^/]+)/) if (match === null) throw new Error(`Cannot extract team name from ${filePath} string`) return match[1] } -export const getJsonPath = (fileMap: FileMap, filePath: string): string => { +export function getJsonPath(fileMap: FileMap, filePath: string): string { let { jsonPathExpression: jsonPath } = fileMap if (fileMap.resourceGroup === 'team') { @@ -283,12 +453,12 @@ export const getJsonPath = (fileMap: FileMap, filePath: string): string => { return jsonPath } -export const loadFileToSpec = async ( +export async function loadFileToSpec( filePath: string, fileMap: FileMap, spec: Record, deps = { loadYaml }, -): Promise => { +): Promise { const jsonPath = getJsonPath(fileMap, filePath) const data = await deps.loadYaml(filePath) if (fileMap.processAs === 'arrayItem') { @@ -302,20 +472,19 @@ export const loadFileToSpec = async ( } } -export const initSpec = (fileMap: FileMap, jsonPath: string, spec: Record) => { - if (fileMap.processAs === 'arrayItem') set(spec, jsonPath, []) - else set(spec, jsonPath, {}) -} - -export const hasCorrespondingDecryptedFile = (filePath: string, fileList: Array): boolean => { - return fileList.includes(`${filePath}.dec`) +export function initSpec(fileMap: FileMap, jsonPath: string, spec: Record) { + if (fileMap.processAs === 'arrayItem') { + set(spec, jsonPath, []) + } else { + set(spec, jsonPath, {}) + } } -export const loadToSpec = async ( +export async function loadToSpec( spec: Record, fileMap: FileMap, deps = { loadFileToSpec }, -): Promise => { +): Promise { const globOptions = { nodir: true, // Exclude directories dot: false, @@ -333,7 +502,7 @@ export const loadToSpec = async ( await Promise.all(promises) } -export const loadValues = async (envDir: string, deps = { loadToSpec }): Promise> => { +export async function loadValues(envDir: string, deps = { loadToSpec }): Promise> { const fileMaps = getFileMaps(envDir) const spec = {} @@ -344,3 +513,19 @@ export const loadValues = async (envDir: string, deps = { loadToSpec }): Promise ) return spec } + +export async function getKmsSettings(envDir: string, deps = { loadToSpec }): Promise> { + const fileMap = getFileMaps(envDir) + const kmsFiles = fileMap.find((item) => item.jsonPathExpression === '$.kms') + const spec = {} + await deps.loadToSpec(spec, kmsFiles!) + return spec +} + +export async function setValuesFile(envDir: string, deps = { pathExists, loadValues, writeFile }): Promise { + const valuesPath = path.join(envDir, 'values-repo.yaml') + if (await deps.pathExists(valuesPath)) return valuesPath + const allValues = await deps.loadValues(envDir) + await deps.writeFile(valuesPath, objectToYaml(allValues)) + return valuesPath +} diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 107c088df..b00de5a02 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -1,5 +1,6 @@ import { Alerts, + App, Build, Cluster, Dns, @@ -29,7 +30,7 @@ export class RepoService { private teamConfigServiceCache = new Map() constructor(private repo: Repo) { - this.repo.apps ??= {} + this.repo.apps ??= [] this.repo.alerts ??= {} as Alerts this.repo.cluster ??= {} as Cluster this.repo.databases ??= {} @@ -65,31 +66,24 @@ export class RepoService { // == APPS CRUD (Dictionary) == // ===================================== - public createApp(key: string, app: any) { - if (has(this.repo.apps, key)) { - throw new Error(`App[${key}] already exists.`) + public getApp(id: string): App { + const app = find(this.repo.apps, { id }) + if (!app) { + throw new Error(`User[${id}] does not exist.`) } - const newApp = { ...app, id: app.id ?? uuidv4() } - this.repo.apps[key] = newApp - return newApp - } - - public getApp(key: string): any | undefined { - return this.repo.apps[key] + return app } - public updateApp(key: string, updates: any): void { - if (!has(this.repo.apps, key)) { - throw new Error(`App[${key}] does not exist.`) - } - merge(this.repo.apps[key], updates) + public getApps(): App[] { + return this.repo.apps } - public deleteApp(key: string): void { - if (!has(this.repo.apps, key)) { - throw new Error(`App[${key}] does not exist.`) + public updateApp(id: string, updates: Partial): App { + const app = find(this.repo.apps, { id }) + if (!app) { + throw new Error(`App[${id}] does not exist.`) } - delete this.repo.apps[key] + return merge(app, updates) } // ===================================== @@ -144,7 +138,7 @@ export class RepoService { projects: [], netpols: [], settings: {} as Team, - apps: {}, + apps: [], policies: {} as Policies, workloadValues: [], } @@ -394,19 +388,19 @@ export class RepoService { } public getAllTeamSettings(): Team[] { - return map(this.repo.teamConfig, 'settings') + return map(this.repo.teamConfig, 'settings').filter(Boolean) } public getAllNetpols(): Netpol[] { - return flatMap(this.repo.teamConfig, 'netpols') + return flatMap(this.repo.teamConfig, 'netpols').filter(Boolean) } public getAllProjects(): Project[] { - return flatMap(this.repo.teamConfig, 'projects') + return flatMap(this.repo.teamConfig, 'projects').filter(Boolean) } public getAllBuilds(): Build[] { - return flatMap(this.repo.teamConfig, 'builds') + return flatMap(this.repo.teamConfig, 'builds').filter(Boolean) } public getAllPolicies(): Record { @@ -414,15 +408,15 @@ export class RepoService { } public getAllWorkloads(): Workload[] { - return flatMap(this.repo.teamConfig, 'workloads') + return flatMap(this.repo.teamConfig, 'workloads').filter(Boolean) } public getAllServices(): Service[] { - return flatMap(this.repo.teamConfig, 'services') + return flatMap(this.repo.teamConfig, 'services').filter(Boolean) } public getAllSealedSecrets(): SealedSecret[] { - return flatMap(this.repo.teamConfig, 'sealedSecrets') + return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) } /** Retrieve a collection dynamically from the Repo */ diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 3273c070a..2335e5997 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -1,4 +1,5 @@ import { + App, Backup, Build, Netpol, @@ -316,35 +317,35 @@ export class TeamConfigService { // == APPS CRUD == // ===================================== - public createApp(key: string, app: any): void { + public createApp(app: App): App { const newApp = { ...app, id: app.id ?? uuidv4() } - if (has(this.teamConfig.apps, key)) { - throw new Error(`App[${key}] already exists.`) + if (find(this.teamConfig.apps, { id: newApp.id })) { + throw new Error(`App[${app.id}] already exists.`) } - this.teamConfig.apps[key] = newApp + this.teamConfig.apps.push(newApp) return newApp } - public getApp(key: string): any | undefined { - return this.teamConfig.apps[key] + public getApp(id: string): App { + const app = find(this.teamConfig.apps, { id }) + if (!app) { + throw new Error(`App[${id}] does not exist.`) + } + return app } - public getApps(): any | undefined { + public getApps(): App[] { return this.teamConfig.apps } - public updateApp(key: string, updates: any): void { - if (!has(this.teamConfig.apps, key)) { - throw new Error(`App[${key}] does not exist.`) - } - merge(this.teamConfig.apps[key], updates) + public updateApp(id: string, updates: Partial): App { + const app = find(this.teamConfig.apps, { id }) + if (!app) throw new Error(`App[${id}] does not exist.`) + return merge(app, updates) } - public deleteApp(key: string): void { - if (!has(this.teamConfig.apps, key)) { - throw new Error(`App[${key}] does not exist.`) - } - delete this.teamConfig.apps[key] + public deleteApp(id: string): void { + remove(this.teamConfig.apps, { id }) } // ===================================== diff --git a/src/utils.ts b/src/utils.ts index ec0991c5f..0c589858a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,11 @@ import axios from 'axios' import cleanDeep, { CleanOptions } from 'clean-deep' import { pathExists } from 'fs-extra' -import { readFile } from 'fs/promises' -import { isArray, memoize, mergeWith, omit } from 'lodash' +import { readdir, readFile } from 'fs/promises' +import { isArray, isEmpty, memoize, mergeWith, omit } from 'lodash' import cloneDeep from 'lodash/cloneDeep' import { Cluster, Dns } from 'src/otomi-models' -import { parse } from 'yaml' +import { parse, stringify } from 'yaml' import { BASEURL } from './constants' export function arrayToObject(array: [] = [], keyName = 'name', keyValue = 'value'): Record { @@ -165,3 +165,17 @@ export const argQuoteStrip = (s: string) => { // use lodash mergeWith to avoid merging arrays export const mergeData = (orig, extra) => mergeWith(orig, extra, (a, b) => (isArray(b) ? b : undefined)) + +export const getDirNames = async (dir: string, opts?: { skipHidden: boolean }): Promise => { + const dirs = await readdir(dir, { withFileTypes: true }) + const dirNames: Array = [] + dirs.map((dirOrFile) => { + if (opts?.skipHidden && dirOrFile.name.startsWith('.')) return + if (dirOrFile.isDirectory()) dirNames.push(dirOrFile.name) + }) + return dirNames +} + +export const objectToYaml = (obj: Record, indent = 4, lineWidth = 200): string => { + return isEmpty(obj) ? '' : stringify(obj, { indent, lineWidth }) +} From 28ce4a456da3ac4f72e883d40cceb7aaa8746c3a Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 20 Feb 2025 15:56:28 +0100 Subject: [PATCH 03/49] feat: save data correctly to files --- src/api/teams/{teamId}/backups/{backupId}.ts | 3 +- src/api/teams/{teamId}/builds/{buildId}.ts | 3 +- src/api/teams/{teamId}/netpols/{netpolId}.ts | 3 +- .../teams/{teamId}/services/{serviceId}.ts | 3 +- .../teams/{teamId}/workloads/{workloadId}.ts | 3 +- .../{teamId}/workloads/{workloadId}/values.ts | 3 +- src/git.ts | 39 +- src/middleware/authz.ts | 12 +- src/middleware/error.ts | 3 +- src/middleware/jwt.ts | 8 +- src/middleware/session.ts | 2 +- src/otomi-stack.ts | 981 +++++++----------- src/services/RepoService.ts | 42 +- src/services/TeamConfigService.ts | 130 ++- src/utils.ts | 1 + 15 files changed, 492 insertions(+), 744 deletions(-) diff --git a/src/api/teams/{teamId}/backups/{backupId}.ts b/src/api/teams/{teamId}/backups/{backupId}.ts index 37f9c1ae8..27b1f72f4 100644 --- a/src/api/teams/{teamId}/backups/{backupId}.ts +++ b/src/api/teams/{teamId}/backups/{backupId}.ts @@ -22,9 +22,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ async ({ otomi, params: { teamId, backupId }, body }: OpenApiRequestExt, res): Promise => { debug(`editBackup(${backupId})`) - const data = await otomi.editBackup(decodeURIComponent(backupId), { + const data = await otomi.editBackup(decodeURIComponent(teamId), decodeURIComponent(backupId), { ...body, - teamId: decodeURIComponent(teamId), } as Backup) res.json(data) }, diff --git a/src/api/teams/{teamId}/builds/{buildId}.ts b/src/api/teams/{teamId}/builds/{buildId}.ts index 7d321ef18..aa45eed55 100644 --- a/src/api/teams/{teamId}/builds/{buildId}.ts +++ b/src/api/teams/{teamId}/builds/{buildId}.ts @@ -22,9 +22,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ ({ otomi, params: { teamId, buildId }, body }: OpenApiRequestExt, res): void => { debug(`editBuild(${buildId})`) - const data = otomi.editBuild(decodeURIComponent(buildId), { + const data = otomi.editBuild(decodeURIComponent(teamId), decodeURIComponent(buildId), { ...body, - teamId: decodeURIComponent(teamId), } as Build) res.json(data) }, diff --git a/src/api/teams/{teamId}/netpols/{netpolId}.ts b/src/api/teams/{teamId}/netpols/{netpolId}.ts index a161f7b1a..9d445e4e4 100644 --- a/src/api/teams/{teamId}/netpols/{netpolId}.ts +++ b/src/api/teams/{teamId}/netpols/{netpolId}.ts @@ -22,9 +22,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ async ({ otomi, params: { teamId, netpolId }, body }: OpenApiRequestExt, res): Promise => { debug(`editNetpol(${netpolId})`) - const data = await otomi.editNetpol(decodeURIComponent(netpolId), { + const data = await otomi.editNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId), { ...body, - teamId: decodeURIComponent(teamId), } as Netpol) res.json(data) }, diff --git a/src/api/teams/{teamId}/services/{serviceId}.ts b/src/api/teams/{teamId}/services/{serviceId}.ts index d15e0defc..b97bfb02d 100644 --- a/src/api/teams/{teamId}/services/{serviceId}.ts +++ b/src/api/teams/{teamId}/services/{serviceId}.ts @@ -22,9 +22,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ async ({ otomi, params: { teamId, serviceId }, body }: OpenApiRequestExt, res): Promise => { debug(`editService(${serviceId})`) - const data = await otomi.editService(decodeURIComponent(serviceId), { + const data = await otomi.editService(decodeURIComponent(teamId), decodeURIComponent(serviceId), { ...body, - teamId: decodeURIComponent(teamId), } as Service) res.json(data) }, diff --git a/src/api/teams/{teamId}/workloads/{workloadId}.ts b/src/api/teams/{teamId}/workloads/{workloadId}.ts index 3faa0a1df..584d3ca85 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}.ts +++ b/src/api/teams/{teamId}/workloads/{workloadId}.ts @@ -22,9 +22,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ async ({ otomi, params: { teamId, workloadId }, body }: OpenApiRequestExt, res): Promise => { debug(`editWorkload(${workloadId})`) - const data = await otomi.editWorkload(decodeURIComponent(workloadId), { + const data = await otomi.editWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId), { ...body, - teamId: decodeURIComponent(teamId), } as Workload) res.json(data) }, diff --git a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts b/src/api/teams/{teamId}/workloads/{workloadId}/values.ts index ae87713d6..aba455112 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts +++ b/src/api/teams/{teamId}/workloads/{workloadId}/values.ts @@ -16,9 +16,8 @@ export default function (): OperationHandlerArray { const put: Operation = [ async ({ otomi, params: { teamId, workloadId }, body }: OpenApiRequestExt, res): Promise => { debug(`putWorkloadValues(${workloadId})`) - const data = await otomi.editWorkloadValues(decodeURIComponent(workloadId), { + const data = await otomi.editWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadId), { ...body, - teamId: decodeURIComponent(teamId), } as WorkloadValues) res.json(data) }, diff --git a/src/git.ts b/src/git.ts index 2a0eb870b..9295156b5 100644 --- a/src/git.ts +++ b/src/git.ts @@ -16,6 +16,7 @@ import { Core } from './otomi-models' import { removeBlankAttributes } from './utils' import { FileMap, getFilePath, renderManifest, renderManifestForSecrets } from './repo' import jsonpath from 'jsonpath' +import { rmSync } from 'fs' const debug = Debug('otomi:repo') @@ -153,6 +154,15 @@ export class Git { } } + async removeDir(dir: string): Promise { + const absolutePath = join(this.path, dir) + const exists = await this.fileExists(dir) + if (exists) { + debug(`Removing directory: ${absolutePath}`) + rmSync(absolutePath, { recursive: true, force: true }) + } + } + async diffFile(file: string, data: Record): Promise { const repoFile: string = this.getSafePath(file) const oldData = await this.readFile(repoFile) @@ -221,7 +231,6 @@ export class Git { } async saveConfigWithSecrets( - inSecretRelativeFilePath: string, config: Record, secretJsonPaths: string[], fileMap: FileMap, @@ -236,14 +245,6 @@ export class Git { } }) - let secretDataRelativePath = `${inSecretRelativeFilePath}${this.secretFilePostfix}` - - if (this.secretFilePostfix) { - const secretExists = await this.fileExists(inSecretRelativeFilePath) - // In case secret file does not exists, create new one and let sops to encrypt it in place - if (!secretExists) secretDataRelativePath = inSecretRelativeFilePath - } - const jsonPathsValuesPublic = jsonpath.nodes(plainData, fileMap.jsonPathExpression) await Promise.all( jsonPathsValuesPublic.map(async (node) => { @@ -260,10 +261,22 @@ export class Git { } }), ) - if (secretData && Object.keys(secretData).length > 0) { - const secretManifest = renderManifestForSecrets(fileMap, secretData) - await this.writeFile(secretDataRelativePath, secretManifest) - } + const jsonPathsValuesSecrets = jsonpath.nodes(secretData, fileMap.jsonPathExpression) + await Promise.all( + jsonPathsValuesSecrets.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') + const manifest = renderManifestForSecrets(fileMap, nodeValue) + await this.writeFile(filePath, manifest) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e + } + }), + ) } isRootClone(): boolean { diff --git a/src/middleware/authz.ts b/src/middleware/authz.ts index 15f7ae405..e11a486c3 100644 --- a/src/middleware/authz.ts +++ b/src/middleware/authz.ts @@ -77,7 +77,12 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS Service: 'services', Team: 'teamConfig', App: 'apps', + Build: 'builds', + Workload: 'workloads', + Settings: 'otomi', + Project: 'projects', } + const teamSpecificCollections = ['builds', 'services', 'workloads', 'projects'] // <-- These are fetched per team const selector = renameKeys(req.params) console.log(schemaName) @@ -90,7 +95,12 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS ) try { - const collection = repoService.getCollection(collectionId) + let collection + if (teamSpecificCollections.includes(collectionId)) { + collection = repoService.getTeamConfigService(teamId).getCollection(collectionId) + } else { + collection = repoService.getCollection(collectionId) + } dataOrig = find(collection, selector) || {} } catch (error) { debug('Error in authzMiddleware', error) diff --git a/src/middleware/error.ts b/src/middleware/error.ts index 273aaa1be..e41618758 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -4,6 +4,7 @@ import { Response } from 'express' import { HttpError, OtomiError } from 'src/error' import { OpenApiRequest } from 'src/otomi-models' import { cleanEnv } from 'src/validators' +import { cleanSession } from './session' const env = cleanEnv({}) @@ -27,6 +28,6 @@ export function errorMiddleware(e, req: OpenApiRequest, res: Response, next): vo msg = `Required property missing! '${requiredProperties}'` } const { otomi } = req as any - // if (otomi?.sessionId) cleanSession(otomi.sessionId as string) + if (otomi?.sessionId) cleanSession(otomi.sessionId as string) res.status(code).json({ error: msg }) } diff --git a/src/middleware/jwt.ts b/src/middleware/jwt.ts index e7452de81..ac1a6117f 100644 --- a/src/middleware/jwt.ts +++ b/src/middleware/jwt.ts @@ -37,8 +37,12 @@ export function getUser(user: JWT, otomi: OtomiStack): SessionUser { const teamId = group.substring(5) if (group.substring(0, 5) === 'team-' && !sessionUser.teams.includes(teamId)) { // we might be assigned team-* without that team yet existing in the values, so ignore those - const existing = otomi.repoService.getTeamConfig(teamId) - if (existing) sessionUser.teams.push(teamId) + if (otomi.isLoaded) { + const existing = otomi.repoService.getTeamConfig(teamId) + if (existing) { + sessionUser.teams.push(teamId) + } + } } }) return sessionUser diff --git a/src/middleware/session.ts b/src/middleware/session.ts index bceb1049b..bf929396d 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -43,7 +43,7 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis debug(`Creating session ${sessionId} for user ${editor}`) sessions[sessionId] = new OtomiStack(editor, sessionId) // init repo without inflating db from files as its slow and we just need a copy of the db - await sessions[sessionId].initGit(true) + await sessions[sessionId].initGit() sessions[sessionId].repoService.setRepo(cloneDeep(readOnlyStack.repoService.getRepo())) } else sessions[sessionId].sessionId = sessionId return sessions[sessionId] diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index f3e38bc88..8db0e22bb 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -39,7 +39,7 @@ import { WorkloadValues, } from 'src/otomi-models' import getRepo, { Git } from 'src/git' -import { arrayToObject, getServiceUrl, getValuesSchema, objectToArray, removeBlankAttributes } from 'src/utils' +import { arrayToObject, getValuesSchema, removeBlankAttributes } from 'src/utils' import { cleanEnv, CUSTOM_ROOT_CA, @@ -108,85 +108,6 @@ const env = cleanEnv({ OBJ_STORAGE_APPS, }) -export function getTeamBackupsFilePath(teamId: string): string { - return `env/teams/backups.${teamId}.yaml` -} -export function getTeamBackupsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.backups` -} - -export function getTeamNetpolsFilePath(teamId: string): string { - return `env/teams/netpols.${teamId}.yaml` -} -export function getTeamNetpolsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.netpols` -} - -export function getTeamSealedSecretsValuesRootPath(teamId: string): string { - return `env/teams/${teamId}/sealedsecrets` -} -export function getTeamSealedSecretsValuesFilePath(teamId: string, sealedSecretsName: string): string { - return `env/teams/${teamId}/sealedsecrets/${sealedSecretsName}` -} -export function getTeamWorkloadsFilePath(teamId: string): string { - return `env/teams/workloads.${teamId}.yaml` -} -export function getTeamWorkloadValuesFilePath(teamId: string, workloadName): string { - return `env/teams/workloads/${teamId}/${workloadName}.yaml` -} - -export function getTeamProjectsFilePath(teamId: string): string { - return `env/teams/projects.${teamId}.yaml` -} - -export function getTeamCodereposFilePath(teamId: string): string { - return `env/teams/coderepos.${teamId}.yaml` -} - -export function getTeamBuildsFilePath(teamId: string): string { - return `env/teams/builds.${teamId}.yaml` -} - -export function getTeamPoliciesFilePath(teamId: string): string { - return `env/teams/policies.${teamId}.yaml` -} - -export function getTeamWorkloadsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.workloads` -} - -export function getTeamProjectsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.projects` -} - -export function getTeamCodereposJsonPath(teamId: string): string { - return `teamConfig.${teamId}.coderepos` -} - -export function getTeamBuildsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.builds` -} - -export function getTeamPoliciesJsonPath(teamId: string): string { - return `teamConfig.${teamId}.policies` -} - -export function getTeamSealedSecretsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.sealedsecrets` -} - -export function getTeamSecretsJsonPath(teamId: string): string { - return `teamConfig.${teamId}.secrets` -} - -export function getTeamServicesFilePath(teamId: string): string { - return `env/teams/services.${teamId}.yaml` -} - -export function getTeamServicesJsonPath(teamId: string): string { - return `teamConfig.${teamId}.services` -} - export const rootPath = '/tmp/otomi/values' export default class OtomiStack { @@ -247,7 +168,7 @@ export default class OtomiStack { this.repoService = repoService return } else { - // We need to map the app values so it adheres the App interface + // We need to map the app values, so it adheres the App interface const rawRepo = await loadValues(this.getRepoPath()) rawRepo.apps = this.transformApps(rawRepo.apps) @@ -261,7 +182,7 @@ export default class OtomiStack { } } - async initGit(skipDbInflation = false): Promise { + async initGit(): Promise { await this.init() // every editor gets their own folder to detect conflicts upon deploy const path = this.getRepoPath() @@ -416,8 +337,7 @@ export default class OtomiStack { debug(`Removing ingress apps for ${id}`) const path = `env/apps/${id}.yaml` const secretsPath = `env/apps/secrets.${id}.yaml` - const teamId = 'admin' - this.repoService.getTeamConfigService(teamId).deleteApp(id) + this.repoService.deleteApp(id) await this.git.removeFile(path) await this.git.removeFile(secretsPath) debug(`Ingress app removed for ${id}`) @@ -461,8 +381,7 @@ export default class OtomiStack { } settings[settingId] = removeBlankAttributes(updatedSettingsData[settingId] as Record) this.repoService.updateSettings(settings) - const secretPaths = this.getSecretPaths() - await this.saveSettings(secretPaths) + await this.saveSettings() await this.doDeployment([ 'alerts', 'cluster', @@ -543,10 +462,8 @@ export default class OtomiStack { let app: App = this.repoService.getApp(id) // Shallow merge, so only first level attributes can be replaced (values, rawValues, etc.) app = { ...app, ...data } - this.repoService.updateApp(id, app) - const secretPaths = this.getSecretPaths() - // also save admin apps - await this.saveAdminApps(secretPaths) + app = this.repoService.updateApp(id, app) + await this.saveAdminApps(app) await this.doDeployment(['apps']) return this.repoService.getApp(id) } @@ -557,58 +474,16 @@ export default class OtomiStack { } async toggleApps(teamId: string, ids: string[], enabled: boolean): Promise { - ids.map((id) => { - const orig = this.repoService.getApp(id) - console.log(JSON.stringify(orig)) - console.log(JSON.stringify(this.canToggleApp(id))) - if (orig && this.canToggleApp(id)) { - const app = this.repoService.updateApp(id, { enabled }) - console.log(JSON.stringify(app)) - } - }) - - const secretPaths = this.getSecretPaths() - // also save admin apps - await this.saveAdminApps(secretPaths) - await this.doDeployment(['apps']) - } - - async loadApp(appInstanceId: string): Promise { - const isIngressApp = appInstanceId.startsWith('ingress-nginx-') - const appId = isIngressApp ? 'ingress-nginx' : appInstanceId - const path = `env/apps/${appInstanceId}.yaml` - const secretsPath = `env/apps/secrets.${appInstanceId}.yaml` - const content = await this.git.loadConfig(path, secretsPath) - let values = content?.apps?.[appInstanceId] ?? {} - if (appInstanceId === 'ingress-nginx-platform') { - const isIngressNginxPlatformAppExists = await this.git.fileExists(`env/apps/ingress-nginx-platform.yaml`) - if (!isIngressNginxPlatformAppExists) { - const defaultIngressNginxContent = await this.git.loadConfig( - `env/apps/ingress-nginx.yaml`, - `env/apps/secrets.ingress-nginx.yaml`, - ) - values = defaultIngressNginxContent?.apps?.['ingress-nginx'] ?? {} - } - } - const rawValues = {} - - let enabled - const app = getAppSchema(appId) - if (app?.properties?.enabled) enabled = !!values.enabled - - // we do not want to send enabled flag to the input forms - delete values.enabled - const teamId = 'admin' - this.repoService.getTeamConfigService(teamId).createApp({ enabled, values, rawValues, id: appId }) - } - - async loadApps(): Promise { - const apps = this.getAppList() await Promise.all( - apps.map(async (appId) => { - await this.loadApp(appId) + ids.map(async (id) => { + const orig = this.repoService.getApp(id) + if (orig && this.canToggleApp(id)) { + const app = this.repoService.updateApp(id, { enabled }) + await this.saveAdminApps(app) + } }), ) + await this.doDeployment(['apps']) } getTeams(): Array { @@ -625,11 +500,12 @@ export default class OtomiStack { } getTeam(id: string): Team { - return this.repoService.getTeamConfigService(id).getSettings() + const team = this.repoService.getTeamConfigService(id).getSettings() + return { ...team, name: id } } async createTeam(data: Team, deploy = true): Promise { - const id = data.id || data.name + const teamName = data.name if (isEmpty(data.password)) { debug(`creating password for team '${data.name}'`) @@ -644,41 +520,40 @@ export default class OtomiStack { }) } - const teamConfig = this.repoService.createTeamConfig(id, data) + const teamConfig = this.repoService.createTeamConfig(teamName, data) + console.log('teamConfig', teamConfig) const team = teamConfig.settings + console.log('team', team) const apps = getAppList() const core = this.getCore() apps.forEach((appId) => { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) // Admin apps are loaded by loadApps function - if (id !== 'admin' && (isShared || inTeamApps)) this.repoService.getTeamConfigService(id).createApp({ id: appId }) + if (teamName !== 'admin' && (isShared || inTeamApps)) + this.repoService.getTeamConfigService(teamName).createApp({ id: appId }) }) if (!data.id) { const policies = getPolicies() - this.repoService.getTeamConfigService(id).updatePolicies(policies) + this.repoService.getTeamConfigService(teamName).updatePolicies(policies) } if (deploy) { - const secretPaths = this.getSecretPaths() - await this.saveTeams(secretPaths) - await this.doDeployment(['settings'], id) + await this.saveTeam(team) + await this.doDeployment(['teamConfig']) } return team } async editTeam(id: string, data: Team): Promise { const team = this.repoService.getTeamConfigService(id).updateSettings(data) - const secretPaths = this.getSecretPaths() - await this.saveTeams(secretPaths) + await this.saveTeam(team) await this.doDeployment(['settings'], id) return team } async deleteTeam(id: string): Promise { - this.repoService.deleteTeamConfig(id) - const secretPaths = this.getSecretPaths() - await this.saveTeams(secretPaths) + await this.deleteTeamConfig(id) await this.doDeployment(['teamConfig']) } @@ -700,7 +575,7 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) - await this.saveTeamBackups(teamId) + await this.saveTeamBackup(teamId, data) await this.doDeployment(['backups'], teamId) return backup } catch (err) { @@ -713,17 +588,17 @@ export default class OtomiStack { return this.repoService.getTeamConfigService(teamId).getBackup(id) } - async editBackup(id: string, data: Backup): Promise { + async editBackup(teamId: string, id: string, data: Backup): Promise { validateBackupFields(data.name, data.ttl) - const backup = this.repoService.getTeamConfigService(data.teamId!).updateBackup(id, data) - await this.saveTeamBackups(data.teamId!) - await this.doDeployment(['backups'], data.teamId) + const backup = this.repoService.getTeamConfigService(teamId).updateBackup(id, data) + await this.saveTeamBackup(teamId, data) + await this.doDeployment(['backups'], teamId) return backup } async deleteBackup(teamId: string, id: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteBackup(id) - await this.saveTeamBackups(teamId) + const backup = this.repoService.getTeamConfigService(teamId).getBackup(id) + await this.deleteTeamBackup(teamId, backup.name) await this.doDeployment(['backups'], teamId) } @@ -739,7 +614,7 @@ export default class OtomiStack { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) - await this.saveTeamNetpols(teamId) + await this.saveTeamNetpols(teamId, data) await this.doDeployment(['netpols'], teamId) return netpol } catch (err) { @@ -752,17 +627,17 @@ export default class OtomiStack { return this.repoService.getTeamConfigService(teamId).getNetpol(id) } - async editNetpol(id: string, data: Netpol): Promise { + async editNetpol(teamId: string, id: string, data: Netpol): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const netpol = this.repoService.getTeamConfigService(data.teamId!).updateNetpol(id, data) - await this.saveTeamNetpols(netpol.teamId!) + const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(id, data) + await this.saveTeamNetpols(teamId, data) await this.doDeployment(['netpols'], data.teamId) return netpol } async deleteNetpol(teamId: string, id: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteNetpol(id) - await this.saveTeamNetpols(teamId) + const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(id) + await this.deleteTeamNetpol(teamId, netpol.name) await this.doDeployment(['netpols'], teamId) } @@ -781,8 +656,7 @@ export default class OtomiStack { async createUser(data: User): Promise { const { valid, error } = isValidUsername(data.email.split('@')[0]) if (!valid) { - const err = new HttpError(400, error as string) - throw err + throw new HttpError(400, error as string) } const initialPassword = generatePassword({ length: 16, @@ -804,11 +678,12 @@ export default class OtomiStack { existingUsersEmail = await getKeycloakUsers(keycloakBaseUrl, realm, username, password) } try { - if (existingUsersEmail.some((existingUser) => existingUser === user.email)) + if (existingUsersEmail.some((existingUser) => existingUser === user.email)) { throw new AlreadyExists('User email already exists') + } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const createdUser = this.repoService.createUser(user) - await this.saveUsers() + await this.saveUser(createdUser) await this.doDeployment(['users']) return createdUser } catch (err) { @@ -824,7 +699,7 @@ export default class OtomiStack { async editUser(id: string, data: User): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const user = this.repoService.updateUser(id, data) - await this.saveUsers() + await this.saveUser(user) await this.doDeployment(['users']) return user } @@ -837,20 +712,19 @@ export default class OtomiStack { error.publicMessage = 'Cannot delete the default platform admin user' throw error } - this.repoService.deleteUser(id) - await this.saveUsers() + await this.deleteUserFile(user) await this.doDeployment(['users']) } async editTeamUsers( data: Pick[], ): Promise> { - data.forEach((user) => { + for (const user of data) { const existingUser = this.repoService.getUser(user.id!) - this.repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) - }) + const updateUser = this.repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) + await this.saveUser(updateUser) + } const users = this.repoService.getUsers() - await this.saveUsers() await this.doDeployment(['users']) return users } @@ -873,11 +747,17 @@ export default class OtomiStack { if (projectNameTaken) throw new AlreadyExists(projectNameTakenPublicMessage) // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const project = this.repoService.getTeamConfigService(teamId).createProject({ ...data, teamId }) - await this.saveTeamProjects(teamId) - await this.saveTeamBuilds(teamId) - await this.saveTeamWorkloads(teamId) - await this.saveTeamServices(teamId) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) + if (data.build) { + await this.createBuild(teamId, data.build) + } + if (data.workload) { + await this.createWorkload(teamId, data.workload) + } + if (data.service) { + await this.createService(teamId, data.service) + } + await this.saveTeamProject(teamId, data) + await this.doDeployment(['projects'], teamId) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -923,7 +803,6 @@ export default class OtomiStack { async editProject(teamId: string, id: string, data: Project): Promise { const { build, workload, workloadValues, service, name } = data - const { values } = workloadValues as WorkloadValues let b, w, wv, s if (!build?.id && build?.mode) { @@ -938,10 +817,12 @@ export default class OtomiStack { w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.id, workload) } - if (!data.workloadValues?.id) { - wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ teamId, values }) + if (workloadValues && !workloadValues?.id) { + wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...workloadValues, name }) } else if (workloadValues?.id) { - wv = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(workloadValues.id, { teamId, values }) + wv = this.repoService + .getTeamConfigService(teamId) + .updateWorkloadValues(workloadValues.id, { ...workloadValues, name }) } if (service && !service?.id) { @@ -960,12 +841,21 @@ export default class OtomiStack { service: { id: s.id }, } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const project = this.repoService.getTeamConfigService(teamId).updateProject(id, updatedData) - await this.saveTeamProjects(teamId) - await this.saveTeamBuilds(teamId) - await this.saveTeamWorkloads(teamId) - await this.saveTeamServices(teamId) + let project: Project + try { + project = this.repoService.getTeamConfigService(teamId).createProject(updatedData) + } catch (error) { + if (error.code === 409) { + project = this.repoService.getTeamConfigService(teamId).updateProject(id, updatedData) + } else { + throw error + } + } + await this.saveTeamProject(teamId, data) + await this.saveTeamBuild(teamId, b) + await this.saveTeamWorkload(teamId, w) + await this.saveTeamWorkloadValues(teamId, wv) + await this.saveTeamService(teamId, s) await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) return project } @@ -973,15 +863,19 @@ export default class OtomiStack { // Deletes a project and all its related resources async deleteProject(teamId: string, id: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProject(id) - if (p.build?.id) this.repoService.getTeamConfigService(teamId).deleteBuild(p.build.id) - if (p.workload?.id) this.repoService.getTeamConfigService(teamId).deleteWorkload(p.workload.id) - if (p.workloadValues?.id) this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(p.workloadValues.id) - if (p.service?.id) this.repoService.getTeamConfigService(teamId).deleteService(p.service.id) - this.repoService.getTeamConfigService(teamId).deleteProject(id) - await this.saveTeamProjects(teamId) - await this.saveTeamBuilds(teamId) - await this.saveTeamWorkloads(teamId) - await this.saveTeamServices(teamId) + if (p.build?.name) { + await this.deleteTeamBuild(teamId, p.build.name) + } + if (p.workload?.name) { + await this.deleteTeamWorkload(teamId, p.workload.name) + } + if (p.workloadValues?.name) { + await this.deleteTeamWorkloadValues(teamId, p.workloadValues.name) + } + if (p.service?.name) { + await this.deleteTeamService(teamId, p.service.name) + } + await this.deleteTeamProject(teamId, p.name) await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } @@ -1101,7 +995,7 @@ export default class OtomiStack { try { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) - await this.saveTeamBuilds(teamId) + await this.saveTeamBuild(teamId, data) await this.doDeployment(['builds'], teamId) return build } catch (err) { @@ -1114,24 +1008,24 @@ export default class OtomiStack { return this.repoService.getTeamConfigService(teamId).getBuild(id) } - async editBuild(id: string, data: Build): Promise { + async editBuild(teamId: string, id: string, data: Build): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const build = this.repoService.getTeamConfigService(data.teamId!).updateBuild(id, data) - await this.saveTeamBuilds(build.teamId!) + const build = this.repoService.getTeamConfigService(teamId).updateBuild(id, data) + await this.saveTeamBuild(teamId, build) await this.doDeployment(['builds'], data.teamId) return build } async deleteBuild(teamId: string, id: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProjects() + const build = this.repoService.getTeamConfigService(teamId).getBuild(id) p.forEach((project: Project) => { if (project?.build?.id === id) { const updatedData = { ...project, build: undefined } this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) - this.repoService.getTeamConfigService(teamId).deleteBuild(id) - await this.saveTeamBuilds(teamId) + await this.deleteTeamBuild(teamId, build.name) await this.doDeployment(['builds'], teamId) } @@ -1157,8 +1051,7 @@ export default class OtomiStack { } async getK8sVersion(): Promise { - const version = (await getKubernetesVersion()) as string - return version + return (await getKubernetesVersion()) as string } async connectCloudtty(data: Cloudtty): Promise { @@ -1254,11 +1147,10 @@ export default class OtomiStack { let url = clientUrl if (env?.HELM_CHART_CATALOG && !clientUrl) url = env.HELM_CHART_CATALOG if (!url) { - const err = { + throw { code: 404, message: 'No helm chart catalog found!', } - throw err } const version = env.VERSIONS.core as string const { helmCharts, catalog } = await fetchWorkloadCatalog(url, sub, teamId, version) @@ -1268,8 +1160,12 @@ export default class OtomiStack { async createWorkload(teamId: string, data: Workload): Promise { try { const workload = this.repoService.getTeamConfigService(teamId).createWorkload({ ...data, teamId }) - this.repoService.getTeamConfigService(teamId).createWorkloadValues({ teamId, values: {}, id: workload.id }) - await this.saveTeamWorkloads(teamId) + console.log('workload', workload.id) + const workloadValues = this.repoService + .getTeamConfigService(teamId) + .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) + await this.saveTeamWorkload(teamId, data) + await this.saveTeamWorkloadValues(teamId, workloadValues) await this.doDeployment(['workloads', 'workloadValues'], teamId) return workload } catch (err) { @@ -1282,10 +1178,10 @@ export default class OtomiStack { return this.repoService.getTeamConfigService(teamId).getWorkload(id) } - async editWorkload(id: string, data: Workload): Promise { - const workload = this.repoService.getTeamConfigService(data.teamId!).updateWorkload(id, data) - await this.saveTeamWorkloads(workload.teamId!) - await this.doDeployment(['workloads', 'workloadValues'], data.teamId) + async editWorkload(teamId: string, id: string, data: Workload): Promise { + const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(id, data) + await this.saveTeamWorkload(teamId, workload) + await this.doDeployment(['workloads'], data.teamId) return workload } @@ -1298,18 +1194,24 @@ export default class OtomiStack { } }) const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) - const path = getTeamWorkloadValuesFilePath(workloadValues.teamId!, workloadValues.name) - await this.git.removeFile(path) - this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(id) - this.repoService.getTeamConfigService(teamId).deleteWorkload(id) - await this.saveTeamWorkloads(workloadValues.teamId!) + await this.deleteTeamWorkloadValues(teamId, workloadValues.name!) + const workload = this.repoService.getTeamConfigService(teamId).getWorkload(id) + await this.deleteTeamWorkload(teamId, workload.name) await this.doDeployment(['workloads', 'workloadValues'], teamId) } - async editWorkloadValues(id: string, data: WorkloadValues): Promise { - const workloadValues = this.repoService.getTeamConfigService(data.teamId!).updateWorkloadValues(id, data) - await this.saveTeamWorkloads(workloadValues.teamId!) - await this.doDeployment(['workloadValues'], workloadValues.teamId) + async editWorkloadValues(teamId: string, id: string, data: WorkloadValues): Promise { + let workloadValues + try { + workloadValues = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...data, id }) + } catch (error) { + if (error.code === 409) { + debug('Workload values already exists, updating values') + workloadValues = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(id, data) + } + } + await this.saveTeamWorkloadValues(teamId, workloadValues) + await this.doDeployment(['workloadValues'], teamId) return workloadValues } @@ -1325,7 +1227,7 @@ export default class OtomiStack { this.checkPublicUrlInUse(teamId, data) try { const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) - await this.saveTeamServices(teamId) + await this.saveTeamService(teamId, data) await this.doDeployment(['services'], teamId) return service } catch (err) { @@ -1338,9 +1240,9 @@ export default class OtomiStack { return this.repoService.getTeamConfigService(teamId).getService(id) } - async editService(id: string, data: Service): Promise { - const service = this.repoService.getTeamConfigService(data.teamId!).updateService(id, data) - await this.saveTeamServices(service.teamId!) + async editService(teamId: string, id: string, data: Service): Promise { + const service = this.repoService.getTeamConfigService(teamId).updateService(id, data) + await this.saveTeamService(teamId, service) await this.doDeployment(['services'], data.teamId) return service } @@ -1355,8 +1257,8 @@ export default class OtomiStack { } }) } - this.repoService.getTeamConfigService(teamId).deleteService(id) - await this.saveTeamServices(teamId) + const service = this.repoService.getTeamConfigService(teamId).getService(id) + await this.deleteTeamService(teamId, service.name) await this.doDeployment(['services'], teamId) } @@ -1425,6 +1327,7 @@ export default class OtomiStack { rootStack.repoService.getTeamConfigService(teamId).updateCollection(collectionId, collection) } else { // Otherwise, update the root repo collection + console.log('collectionId', collectionId) const collection = this.repoService.getCollection(collectionId) if (collection) { rootStack.repoService.updateCollection(collectionId, collection) @@ -1561,8 +1464,7 @@ export default class OtomiStack { const secretName = 'harbor-pushsecret' const secretRes = await client.readNamespacedSecret(secretName, namespace) const { body: secret }: { body: k8s.V1Secret } = secretRes - const token = Buffer.from(secret.data!['.dockerconfigjson'], 'base64').toString('ascii') - return token + return Buffer.from(secret.data!['.dockerconfigjson'], 'base64').toString('ascii') } async createSealedSecret(teamId: string, data: SealedSecret): Promise { @@ -1630,8 +1532,7 @@ export default class OtomiStack { key, value: secretValues?.[key] || value, })) - const res = { ...sealedSecret, encryptedData, isDisabled } as any - return res + return { ...sealedSecret, encryptedData, isDisabled } as any } getAllSealedSecrets(): Array { @@ -1651,350 +1552,220 @@ export default class OtomiStack { debug('Loading values') await this.git.initSops() await this.initRepo() - // await this.loadCluster() - // await this.loadSettings() - // await this.loadUsers() - // await this.loadTeams() - // await this.loadApps() this.isLoaded = true } - async loadCluster(): Promise { - const { cluster } = await this.git.loadConfig('env/cluster.yaml', 'env/secrets.cluster.yaml') - this.repoService.updateCluster(cluster) - } - - async loadSettings(): Promise { - const data: Record = await this.git.loadConfig('env/settings.yaml', `env/secrets.settings.yaml`) + async saveAdminApps(app: App, secretPaths?: string[]): Promise { + const apps = {} + const { id, enabled, values, rawValues } = app + apps[id] = { + ...(values || {}), + } + if (!isEmpty(rawValues)) { + apps[id]._rawValues = rawValues + } - if (data.otomi) { - data.otomi.nodeSelector = objectToArray((data.otomi.nodeSelector ?? {}) as Record) + if (this.canToggleApp(id)) { + apps[id].enabled = !!enabled + } else { + delete apps[id].enabled } - this.repoService.updateSettings(data) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplApp')! + await this.git.saveConfigWithSecrets({ apps }, secretPaths ?? this.getSecretPaths(), fileMap) } - async loadUsers(): Promise { - const { secretFilePostfix } = this.git - const relativePath = `env/secrets.users.yaml` - const secretRelativePath = `${relativePath}${secretFilePostfix}` - if (!(await this.git.fileExists(relativePath)) || !(await this.git.fileExists(secretRelativePath))) { - debug(`No users found`) - return + async saveSettings(secretPaths?: string[]): Promise { + const settings = cloneDeep(this.getSettings()) as Record> + settings.otomi.nodeSelector = arrayToObject(settings.otomi.nodeSelector as []) + const fileMaps = getFileMaps('').filter((fm) => fm.resourceDir === 'settings')! + for (const fileMap of fileMaps) { + await this.git.saveConfigWithSecrets(settings, secretPaths ?? this.getSecretPaths(), fileMap) } - const data = await this.git.readFile(secretRelativePath) - const inData: Array = get(data, `users`, []) - inData.forEach((inUser) => { - const user = this.repoService.createUser(inUser) - debug(`Loaded user: email: ${user.email}, id: ${user.id}`) - }) } - async loadTeamSealedSecrets(teamId: string): Promise { - const sealedSecretsValuesRootPath = getTeamSealedSecretsValuesRootPath(teamId) - const sealedSecretsFileNames = await this.repo.readDir(sealedSecretsValuesRootPath) - if (sealedSecretsFileNames.length === 0) return - + async saveUser(user: User): Promise { + debug(`Saving user ${user.email}`) + const users: User[] = [] + users.push(user) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplUser')! + const jsonPathsValuesSecrets = jsonpath.nodes({ users }, fileMap.jsonPathExpression) + //TODO clean this up await Promise.all( - sealedSecretsFileNames.map(async (id: string) => { - const relativePath = getTeamSealedSecretsValuesFilePath(teamId, id) - if (!(await this.repo.fileExists(relativePath))) { - debug(`Team ${teamId} has no sealed secrets yet`) - return + jsonPathsValuesSecrets.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') + const manifest = renderManifestForSecrets(fileMap, nodeValue) + await this.git.writeFile(filePath, manifest, false) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e } - const data = await this.repo.readFile(relativePath) - const res: any = this.db.populateItem( - 'sealedsecrets', - { - encryptedData: data.spec.encryptedData, - metadata: data.spec.template.metadata, - type: data.spec.template.type, - name: data.metadata.name, - teamId, - }, - undefined, - id.replace('.yaml', ''), - ) - debug(`Loaded sealed secret: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) }), ) } - async loadTeamBackups(teamId: string): Promise { - const relativePath = getTeamBackupsFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no backups yet`) - return - } - const data = await this.git.readFile(relativePath) - const inData: Array = get(data, getTeamBackupsJsonPath(teamId), []) - inData.forEach((inBackup) => { - const backup = this.repoService.getTeamConfigService(teamId).createBackup({ ...inBackup, teamId }) - debug(`Loaded backup: name: ${backup.name}, id: ${backup.id}, teamId: ${backup.teamId}`) - }) + async deleteUserFile(user: User): Promise { + debug(`Deleting user ${user.email}`) + this.repoService.deleteUser(user.id!) + const users: User[] = [] + users.push(user) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplUser')! + const jsonPathsValuesSecrets = jsonpath.nodes({ users }, fileMap.jsonPathExpression) + await Promise.all( + jsonPathsValuesSecrets.map(async (node) => { + const nodePath = node.path + const nodeValue = node.value + try { + const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') + await this.git.removeFile(filePath) + } catch (e) { + console.log(nodePath) + console.log(fileMap) + throw e + } + }), + ) } - async loadTeamNetpols(teamId: string): Promise { - const relativePath = getTeamNetpolsFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no network policies yet`) - return + async saveTeam(team: Team, secretPaths?: string[]): Promise { + debug(`Saving team ${team.name}`) + const repo = { + teamConfig: { + [team.name]: { + settings: team, + }, + }, } - const data = await this.git.readFile(relativePath) - const inData: Array = get(data, getTeamNetpolsJsonPath(teamId), []) - inData.forEach((inNetpol) => { - const netpol = this.repoService.getTeamConfigService(teamId).createNetpol({ ...inNetpol, teamId }) - debug(`Loaded network policy: name: ${netpol.name}, id: ${netpol.id}, teamId: ${netpol.teamId}`) - }) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSettingSet')! + await this.git.saveConfigWithSecrets(repo, secretPaths ?? this.getSecretPaths(), fileMap) } - async loadTeamProjects(teamId: string): Promise { - const relativePath = getTeamProjectsFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no projects yet`) - return - } - const data = await this.git.readFile(relativePath) - const inData: Array = get(data, getTeamProjectsJsonPath(teamId), []) - inData.forEach((inProject) => { - const project = this.repoService.getTeamConfigService(teamId).createProject({ ...inProject, teamId }) - debug(`Loaded project: name: ${project.name}, id: ${project.id}, teamId: ${project.teamId}`) - }) + async deleteTeamConfig(name: string): Promise { + this.repoService.deleteTeamConfig(name) + const teamDir = `env/teams/${name}` + await this.git.removeDir(teamDir) } - async loadTeamCoderepos(teamId: string): Promise { - const relativePath = getTeamCodereposFilePath(teamId) - if (!(await this.repo.fileExists(relativePath))) { - debug(`Team ${teamId} has no coderepos yet`) - return + async saveTeamSealedSecrets(teamId: string, sealedSecret: SealedSecret): Promise { + debug(`Saving sealed secret ${sealedSecret.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + sealedSecrets: [sealedSecret], + }, + }, } - const data = await this.repo.readFile(relativePath) - const inData: Array = get(data, getTeamCodereposJsonPath(teamId), []) - inData.forEach((inCoderepo) => { - const res: any = this.db.populateItem('coderepos', { ...inCoderepo, teamId }, undefined, inCoderepo.id as string) - debug(`Loaded coderepo: name: ${res.name}, id: ${res.id}, teamId: ${res.teamId}`) - }) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! + await this.git.saveConfig(repo, fileMap) } - async loadTeamBuilds(teamId: string): Promise { - const relativePath = getTeamBuildsFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no builds yet`) - return - } - const data = await this.git.readFile(relativePath) - const inData: Array = get(data, getTeamBuildsJsonPath(teamId), []) - inData.forEach((inBuild) => { - const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...inBuild, teamId }) - debug(`Loaded build: name: ${build.name}, id: ${build.id}, teamId: ${build.teamId}`) - }) - } + async deleteTeamSealedSecrets(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) - async loadTeamPolicies(teamId: string): Promise { - const relativePath = getTeamPoliciesFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no policies yet`) - return - } - const data = await this.git.readFile(relativePath) - const inData: Partial = get(data, getTeamPoliciesJsonPath(teamId), {}) - this.repoService.getTeamConfigService(teamId).updatePolicies(inData) - debug(`Loaded policies of team: ${teamId}`) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - async loadTeamWorkloads(teamId: string): Promise { - const relativePath = getTeamWorkloadsFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no workloads yet`) - return + async saveTeamBackup(teamId: string, backup: Backup): Promise { + debug(`Saving backup ${backup.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + backups: [backup], + }, + }, } - const data = await this.git.readFile(relativePath) - const inData: Array = get(data, getTeamWorkloadsJsonPath(teamId), []) - inData.forEach((inWorkload) => { - const workload = this.repoService.getTeamConfigService(teamId).createWorkload({ ...inWorkload, teamId }) - debug(`Loaded workload: name: ${workload.name}, id: ${workload.id}, teamId: ${workload.teamId}`) - }) - const workloads = this.getTeamWorkloads(teamId) - await Promise.all( - workloads.map((workload) => { - this.loadWorkloadValues(workload) - }), - ) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! + await this.git.saveConfig(repo, fileMap) } - async loadWorkloadValues(workload: Workload): Promise { - const relativePath = getTeamWorkloadValuesFilePath(workload.teamId!, workload.name) - let data = { values: {} } as WorkloadValues - if (!(await this.git.fileExists(relativePath))) debug(`The workload values file does not exists at ${relativePath}`) - else { - const fileData = await this.git.readFile(relativePath) - data = { ...data, ...fileData } - } - - data.id = workload.id! - data.teamId = workload.teamId! - data.name = workload.name! - try { - data.values = parseYaml(data.values as unknown as string) || {} - } catch (error) { - debug( - `The values property does not seem to be a YAML formated string at ${relativePath}. Falling back to empty map.`, - ) - data.values = {} - } - - const res = this.repoService.getTeamConfigService(workload.teamId!).createWorkloadValues(data) - debug(`Loaded workload values: name: ${res.name} id: ${res.id}, teamId: ${workload.teamId!}`) - return res - } + async deleteTeamBackup(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteBackup(name) - async loadTeams(): Promise { - const mergedData: Core = await this.git.loadConfig('env/teams.yaml', `env/secrets.teams.yaml`) - const tc = mergedData?.teamConfig || {} - if (!tc.admin) tc.admin = { id: 'admin' } - await Promise.all( - Object.values(tc).map(async (team: Team) => { - await this.loadTeam(team) - this.loadTeamNetpols(team.id!) - this.loadTeamServices(team.id!) - this.loadTeamSealedSecrets(team.id!) - this.loadTeamWorkloads(team.id!) - this.loadTeamBackups(team.id!) - this.loadTeamProjects(team.id!) - this.loadTeamCoderepos(team.id!) - this.loadTeamBuilds(team.id!) - this.loadTeamPolicies(team.id!) - }), - ) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - async loadTeamServices(teamId: string): Promise { - const relativePath = getTeamServicesFilePath(teamId) - if (!(await this.git.fileExists(relativePath))) { - debug(`Team ${teamId} has no services yet`) - return + async saveTeamNetpols(teamId: string, netpol: Netpol): Promise { + debug(`Saving netpols ${netpol.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + netpols: [netpol], + }, + }, } - const data = await this.git.readFile(relativePath) - const services = get(data, getTeamServicesJsonPath(teamId), []) - services.forEach((svc) => { - this.loadService(svc, teamId) - }) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! + await this.git.saveConfig(repo, fileMap) } - async saveAdminApps(secretPaths?: string[]): Promise { - await Promise.all( - this.getApps('admin').map(async (app) => { - const apps = {} - const { id, enabled, values, rawValues } = app - apps[id] = { - ...(values || {}), - } - if (!isEmpty(rawValues)) apps[id]._rawValues = rawValues - - if (this.canToggleApp(id)) apps[id].enabled = !!enabled - else delete apps[id].enabled - - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplApp')! - await this.git.saveConfigWithSecrets( - `env/apps/secrets.${id}.yaml`, - { apps }, - secretPaths ?? this.getSecretPaths(), - fileMap, - ) - }), - ) - } + async deleteTeamNetpol(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteNetpol(name) - async saveSettings(secretPaths?: string[]): Promise { - const settings = cloneDeep(this.getSettings()) as Record> - settings.otomi.nodeSelector = arrayToObject(settings.otomi.nodeSelector as []) - //TODO this one needs to be done for all settings - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplSmtp')! - await this.git.saveConfigWithSecrets( - `env/secrets.settings.yaml`, - omit(settings, ['cluster']), - secretPaths ?? this.getSecretPaths(), - fileMap, - ) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - async saveUsers(): Promise { - const users = this.repoService.getUsers() - debug(`Saving users`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplUser')! - const jsonPathsvaluesSecrets = jsonpath.nodes(users, fileMap.jsonPathExpression) - //TODO check what to do when we delete a user - await Promise.all( - jsonPathsvaluesSecrets.map(async (node) => { - const nodePath = node.path - const nodeValue = node.value - try { - const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') - const manifest = renderManifestForSecrets(fileMap, nodeValue) - await this.git.writeFile(filePath, manifest, false) - } catch (e) { - console.log(nodePath) - console.log(fileMap) - throw e - } - }), - ) + async saveTeamWorkload(teamId: string, workload: Workload): Promise { + debug(`Saving workload ${workload.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + workloads: [workload], + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! + await this.git.saveConfig(repo, fileMap) } - async saveTeams(secretPaths?: string[]): Promise { - const secretFilePath = `env/secrets.teams.yaml` - const teamValues = {} - const teams = this.getTeams() - await Promise.all( - teams.map((inTeam) => { - const team: Record = omit(inTeam, 'name') - const teamId = team.id as string - team.resourceQuota = arrayToObject((team.resourceQuota as []) ?? []) - teamValues[teamId] = team - }), - ) - const values = set({}, 'teamConfig', teamValues) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamSettingSet')! - await this.git.saveConfigWithSecrets(secretFilePath, values, secretPaths ?? this.getSecretPaths(), fileMap) - } + async deleteTeamWorkload(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteWorkload(name) - async saveTeamSealedSecrets(teamId: string, data: any, id: string): Promise { - const relativePath = getTeamSealedSecretsValuesFilePath(teamId, `${id}.yaml`) - debug(`Saving sealed secrets of team: ${teamId}`) - await this.repo.writeFile(relativePath, data) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - async saveTeamBackups(teamId: string): Promise { - const backups = this.repoService.getTeamConfigService(teamId).getBackups() - debug(`Saving backups of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamBackup')! - await this.git.saveConfig(backups, fileMap) + async saveTeamProject(teamId: string, project: Project): Promise { + debug(`Saving project ${project.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + projects: [project], + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! + await this.git.saveConfig(repo, fileMap) } - async saveTeamNetpols(teamId: string): Promise { - const netpols = this.repoService.getTeamConfigService(teamId).getNetpols() - debug(`Saving network policies of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamNetworkControl')! - await this.git.saveConfig(netpols, fileMap) - } + async deleteTeamProject(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteProject(name) - async saveTeamWorkloads(teamId: string): Promise { - const workloads = this.repoService.getTeamConfigService(teamId).getWorkloads() - debug(`Saving workloads of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamWorkload')! - await this.git.saveConfig(workloads, fileMap) - await Promise.all( - workloads.map((workload) => { - this.saveWorkloadValues(teamId, workload) - }), - ) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - async saveTeamProjects(teamId: string): Promise { - const projects = this.repoService.getTeamConfigService(teamId).getProjects() - debug(`Saving projects of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamProject')! - await this.git.saveConfig(projects, fileMap) + async saveTeamBuild(teamId: string, build: Build): Promise { + debug(`Saving build ${build.name} for team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + builds: [build], + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! + await this.git.saveConfig(repo, fileMap) } async saveTeamCoderepos(teamId: string): Promise { @@ -2008,120 +1779,72 @@ export default class OtomiStack { await this.repo.writeFile(relativePath, outData) } - async saveTeamBuilds(teamId: string): Promise { - const builds = this.repoService.getTeamConfigService(teamId).getBuilds() - debug(`Saving builds of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamBuild')! - await this.git.saveConfig(builds, fileMap) + + async deleteTeamBuild(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteBuild(name) + + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } async saveTeamPolicies(teamId: string): Promise { const policies = this.getTeamPolicies(teamId) - debug(`Saving policies of team: ${teamId}`) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamPolicy')! - await this.git.saveConfig(policies, fileMap) + debug(`Saving team ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + policies, + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamPolicy')! + await this.git.saveConfig(repo, fileMap) } - async saveWorkloadValues(teamId: string, workload: Workload): Promise { - debug(`Saving workload values: id: ${workload.id!} teamId: ${teamId} name: ${workload.name}`) - const data = this.getWorkloadValues(teamId, workload.id!) - const outData = omit(data, ['id', 'teamId', 'name']) as Record + async saveTeamWorkloadValues(teamId: string, workloadValues: WorkloadValues): Promise { + debug(`Saving workload values: ${workloadValues.id!} teamId: ${teamId} name: ${workloadValues.name}`) + const data = this.getWorkloadValues(teamId, workloadValues.id!) + const outData = cloneDeep(data) as Record outData.values = stringifyYaml(data.values, undefined, 4) - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamWorkloadValues')! - await this.git.saveConfig(outData, fileMap) + const repo = { + teamConfig: { + [teamId]: { + workloadValues: [outData], + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! + await this.git.saveConfig(repo, fileMap) } - async saveTeamServices(teamId: string): Promise { - const services = this.repoService.getTeamConfigService(teamId).getServices() - //TODO do we need this - const data = {} - const values: any[] = [] - services.forEach((service) => { - const value = this.convertDbServiceToValues(service) - values.push(value) - }) - set(data, getTeamServicesJsonPath(teamId), values) - - const fileMap = getFileMaps(this.getRepoPath()).find((fm) => fm.kind === 'AplTeamService')! - await this.git.saveConfig(services, fileMap) - } - - async loadTeam(inTeam: Team): Promise { - const team = { ...inTeam, name: inTeam.id } as Record - team.resourceQuota = objectToArray(inTeam.resourceQuota as Record) - const res = await this.createTeam(team as Team, false) - // const res: any = this.db.populateItem('teams', { ...team, name: team.id! }, undefined, team.id as string) - debug(`Loaded team: ${res.id!}`) - } - - loadService(svcRaw, teamId: string): void { - // Create service - const svc = omit( - svcRaw, - 'certArn', - 'certName', - 'domain', - 'forwardPath', - 'hasCert', - 'paths', - 'type', - 'ownHost', - 'tlsPass', - 'ingressClassName', - 'headers', - 'useCname', - 'cname', - ) - svc.teamId = teamId - svc.name = svcRaw.name as string - if (!('name' in svcRaw)) debug('Unknown service structure') - if (svcRaw.type === 'cluster') svc.ingress = { type: 'cluster' } - else { - const { cluster, dns } = this.getSettings(['cluster', 'dns']) - const url = getServiceUrl({ domain: svcRaw.domain, name: svcRaw.name, teamId, cluster, dns }) - // TODO remove the isArray check in 0.5.24 - const headers = isArray(svcRaw.headers) ? undefined : svcRaw.headers - svc.ingress = { - certName: svcRaw.certName || undefined, - domain: url.domain, - headers, - forwardPath: 'forwardPath' in svcRaw, - hasCert: 'hasCert' in svcRaw, - paths: svcRaw.paths ? svcRaw.paths : [], - subdomain: url.subdomain, - tlsPass: 'tlsPass' in svcRaw, - type: svcRaw.type, - useDefaultHost: !svcRaw.domain && svcRaw.ownHost, - ingressClassName: svcRaw.ingressClassName || undefined, - useCname: svcRaw.useCname, - cname: svcRaw.cname, - } - } + async deleteTeamWorkloadValues(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(name) - const res: any = this.repoService.getTeamConfigService(teamId).createService(removeBlankAttributes(svc)) - debug(`Loaded service: name: ${res.name}, id: ${res.id}`) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } - // eslint-disable-next-line class-methods-use-this - convertDbServiceToValues(svc: any): any { - const svcCloned = omit(svc, ['teamId', 'ingress', 'path']) - if (svc.ingress && svc.ingress.type !== 'cluster') { - const ing = svc.ingress - if (ing.useDefaultHost) svcCloned.ownHost = true - else svcCloned.domain = ing.subdomain ? `${ing.subdomain}.${ing.domain}` : ing.domain - if (ing.hasCert) svcCloned.hasCert = true - if (ing.certName) svcCloned.certName = ing.certName - if (ing.certArn) svcCloned.certArn = ing.certArn - if (ing.paths) svcCloned.paths = ing.paths - if (ing.forwardPath) svcCloned.forwardPath = true - if (ing.tlsPass) svcCloned.tlsPass = true - if (ing.ingressClassName) svcCloned.ingressClassName = ing.ingressClassName - if (ing.headers) svcCloned.headers = ing.headers - if (ing.useCname) svcCloned.useCname = ing.useCname - if (ing.cname) svcCloned.cname = ing.cname - svcCloned.type = svc.ingress.type - } else svcCloned.type = 'cluster' - return svcCloned + async saveTeamService(teamId: string, service: Service): Promise { + debug(`Saving service: ${service.name} teamId: ${teamId}`) + const repo = { + teamConfig: { + [teamId]: { + services: [service], + }, + }, + } + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! + await this.git.saveConfig(repo, fileMap) + } + + async deleteTeamService(teamId: string, name: string): Promise { + this.repoService.getTeamConfigService(teamId).deleteService(name) + + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! + const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) + await this.git.removeFile(filePath) } async getSession(user: k8s.User): Promise { diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index b00de5a02..4f5d26a5c 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -24,6 +24,7 @@ import { import { TeamConfigService } from './TeamConfigService' import { find, flatMap, has, map, mapValues, merge, remove } from 'lodash' import { v4 as uuidv4 } from 'uuid' +import { AlreadyExists } from '../error' export class RepoService { // We can create an LRU cache if needed with a lot of teams. @@ -75,7 +76,7 @@ export class RepoService { } public getApps(): App[] { - return this.repo.apps + return this.repo.apps ?? [] } public updateApp(id: string, updates: Partial): App { @@ -86,14 +87,18 @@ export class RepoService { return merge(app, updates) } + public deleteApp(id: string): void { + remove(this.repo.apps, { id }) + } + // ===================================== // == USERS CRUD (Array) == // ===================================== public createUser(user: User): User { const newUser = { ...user, id: user.id ?? uuidv4() } - if (find(this.repo.users, { id: newUser.id })) { - throw new Error(`User[${user.id}] already exists.`) + if (find(this.repo.users, { email: newUser.email })) { + throw new AlreadyExists(`User[${user.email}] already exists.`) } this.repo.users.push(newUser) return newUser @@ -108,7 +113,7 @@ export class RepoService { } public getUsers(): User[] { - return this.repo.users + return this.repo.users ?? [] } public getUsersEmail(): string[] { @@ -144,14 +149,15 @@ export class RepoService { } } - public createTeamConfig(teamId: string, team: Team): TeamConfig { - if (has(this.repo.teamConfig, teamId)) { - throw new Error(`TeamConfig[${teamId}] already exists.`) + public createTeamConfig(teamName: string, team: Team): TeamConfig { + if (has(this.repo.teamConfig, teamName)) { + throw new AlreadyExists(`TeamConfig[${teamName}] already exists.`) } - const newTeam = merge({}, this.getDefaultTeamConfig(), team) - newTeam.id ??= uuidv4() - this.repo.teamConfig[teamId] = newTeam - return this.repo.teamConfig[teamId] + const newTeam = this.getDefaultTeamConfig() + newTeam.settings = team + newTeam.settings.id = teamName + this.repo.teamConfig[teamName] = newTeam + return this.repo.teamConfig[teamName] } public getTeamConfig(teamId: string): TeamConfig | undefined { @@ -388,19 +394,19 @@ export class RepoService { } public getAllTeamSettings(): Team[] { - return map(this.repo.teamConfig, 'settings').filter(Boolean) + return map(this.repo.teamConfig, 'settings').filter(Boolean) ?? [] } public getAllNetpols(): Netpol[] { - return flatMap(this.repo.teamConfig, 'netpols').filter(Boolean) + return flatMap(this.repo.teamConfig, 'netpols').filter(Boolean) ?? [] } public getAllProjects(): Project[] { - return flatMap(this.repo.teamConfig, 'projects').filter(Boolean) + return flatMap(this.repo.teamConfig, 'projects').filter(Boolean) ?? [] } public getAllBuilds(): Build[] { - return flatMap(this.repo.teamConfig, 'builds').filter(Boolean) + return flatMap(this.repo.teamConfig, 'builds').filter(Boolean) ?? [] } public getAllPolicies(): Record { @@ -408,15 +414,15 @@ export class RepoService { } public getAllWorkloads(): Workload[] { - return flatMap(this.repo.teamConfig, 'workloads').filter(Boolean) + return flatMap(this.repo.teamConfig, 'workloads').filter(Boolean) ?? [] } public getAllServices(): Service[] { - return flatMap(this.repo.teamConfig, 'services').filter(Boolean) + return flatMap(this.repo.teamConfig, 'services').filter(Boolean) ?? [] } public getAllSealedSecrets(): SealedSecret[] { - return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) + return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) ?? [] } /** Retrieve a collection dynamically from the Repo */ diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 2335e5997..ea745ff60 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -14,7 +14,7 @@ import { WorkloadValues, } from '../otomi-models' import { find, has, merge, remove } from 'lodash' -import { NotExistError } from '../error' +import { AlreadyExists, NotExistError } from '../error' import { v4 as uuidv4 } from 'uuid' export class TeamConfigService { @@ -25,9 +25,10 @@ export class TeamConfigService { // ===================================== public createBuild(build: Build): Build { + this.teamConfig.builds ??= [] const newBuild = { ...build, id: build.id ?? uuidv4() } - if (find(this.teamConfig.builds, { id: newBuild.id })) { - throw new Error(`Build[${newBuild.id}] already exists.`) + if (find(this.teamConfig.builds, { name: newBuild.name })) { + throw new AlreadyExists(`Build[${newBuild.name}] already exists.`) } this.teamConfig.builds.push(newBuild) return newBuild @@ -42,17 +43,17 @@ export class TeamConfigService { } public getBuilds(): Build[] { - return this.teamConfig.builds + return this.teamConfig.builds ?? [] } public updateBuild(id: string, updates: Partial): Build { const build = find(this.teamConfig.builds, { id }) - if (!build) throw new Error(`Build[${id}] does not exist.`) + if (!build) throw new NotExistError(`Build[${id}] does not exist.`) return merge(build, updates) } - public deleteBuild(id: string): void { - remove(this.teamConfig.builds, { id }) + public deleteBuild(name: string): void { + remove(this.teamConfig.builds, { name }) } // ===================================== @@ -60,9 +61,10 @@ export class TeamConfigService { // ===================================== public createWorkload(workload: Workload): Workload { + this.teamConfig.workloads ??= [] const newWorkload = { ...workload, id: workload.id ?? uuidv4() } - if (find(this.teamConfig.workloads, { id: newWorkload.id })) { - throw new Error(`Workload[${newWorkload.id}] already exists.`) + if (find(this.teamConfig.workloads, { name: newWorkload.name })) { + throw new AlreadyExists(`Workload[${newWorkload.name}] already exists.`) } this.teamConfig.workloads.push(newWorkload) @@ -78,17 +80,17 @@ export class TeamConfigService { } public getWorkloads(): Workload[] { - return this.teamConfig.workloads + return this.teamConfig.workloads ?? [] } public updateWorkload(id: string, updates: Partial): Workload { const workload = find(this.teamConfig.workloads, { id }) - if (!workload) throw new Error(`Workload[${id}] does not exist.`) + if (!workload) throw new NotExistError(`Workload[${id}] does not exist.`) return merge(workload, updates) } - public deleteWorkload(id: string): void { - remove(this.teamConfig.workloads, { id }) + public deleteWorkload(name: string): void { + remove(this.teamConfig.workloads, { name }) } // ===================================== @@ -96,9 +98,10 @@ export class TeamConfigService { // ===================================== public createWorkloadValues(workloadValues: WorkloadValues): WorkloadValues { + this.teamConfig.workloadValues ??= [] const newWorkloadValues = { ...workloadValues, id: workloadValues.id ?? uuidv4() } - if (find(this.teamConfig.workloadValues, { id: newWorkloadValues.id })) { - throw new Error(`WorkloadValues[${newWorkloadValues.id}] already exists.`) + if (find(this.teamConfig.workloadValues, { name: newWorkloadValues.name })) { + throw new AlreadyExists(`WorkloadValues[${newWorkloadValues.name}] already exists.`) } this.teamConfig.workloadValues.push(newWorkloadValues) return newWorkloadValues @@ -114,12 +117,12 @@ export class TeamConfigService { public updateWorkloadValues(id: string, updates: Partial): WorkloadValues { const workloadValues = find(this.teamConfig.workloadValues, { id }) - if (!workloadValues) throw new Error(`WorkloadValues[${id}] does not exist.`) + if (!workloadValues) throw new NotExistError(`WorkloadValues[${id}] does not exist.`) return merge(workloadValues, updates) } - public deleteWorkloadValues(id: string): void { - remove(this.teamConfig.workloadValues, { id }) + public deleteWorkloadValues(name: string): void { + remove(this.teamConfig.workloadValues, { name }) } // ===================================== @@ -127,9 +130,10 @@ export class TeamConfigService { // ===================================== public createService(service: Service): Service { + this.teamConfig.services ??= [] const newService = { ...service, id: service.id ?? uuidv4() } - if (find(this.teamConfig.services, { id: newService.id })) { - throw new Error(`Service[${newService.id}] already exists.`) + if (find(this.teamConfig.services, { name: newService.name })) { + throw new AlreadyExists(`Service[${newService.name}] already exists.`) } this.teamConfig.services.push(newService) return newService @@ -144,16 +148,16 @@ export class TeamConfigService { } public getServices(): Service[] { - return this.teamConfig.services + return this.teamConfig.services ?? [] } public updateService(id: string, updates: Partial): Service { const service = find(this.teamConfig.services, { id }) - if (!service) throw new Error(`Service[${id}] does not exist.`) + if (!service) throw new NotExistError(`Service[${id}] does not exist.`) return merge(service, updates) } - public deleteService(id: string): void { - remove(this.teamConfig.services, { id }) + public deleteService(name: string): void { + remove(this.teamConfig.services, { name }) } // ===================================== @@ -161,9 +165,10 @@ export class TeamConfigService { // ===================================== public createSealedSecret(secret: SealedSecret): SealedSecret { + this.teamConfig.sealedSecrets ??= [] const newSecret = { ...secret, id: secret.id ?? uuidv4() } - if (find(this.teamConfig.sealedSecrets, { id: newSecret.id })) { - throw new Error(`SealedSecret[${newSecret.id}] already exists.`) + if (find(this.teamConfig.sealedSecrets, { name: newSecret.name })) { + throw new AlreadyExists(`SealedSecret[${newSecret.name}] already exists.`) } this.teamConfig.sealedSecrets.push(newSecret) return newSecret @@ -178,17 +183,17 @@ export class TeamConfigService { } public getSealedSecrets(): SealedSecret[] { - return this.teamConfig.sealedSecrets + return this.teamConfig.sealedSecrets ?? [] } public updateSealedSecret(id: string, updates: Partial): SealedSecret { const secret = find(this.teamConfig.sealedSecrets, { id }) - if (!secret) throw new Error(`SealedSecret[${id}] does not exist.`) + if (!secret) throw new NotExistError(`SealedSecret[${id}] does not exist.`) return merge(secret, updates) } - public deleteSealedSecret(id: string): void { - remove(this.teamConfig.sealedSecrets, { id }) + public deleteSealedSecret(name: string): void { + remove(this.teamConfig.sealedSecrets, { name }) } // ===================================== @@ -196,9 +201,10 @@ export class TeamConfigService { // ===================================== public createBackup(backup: Backup): Backup { + this.teamConfig.backups ??= [] const newBackup = { ...backup, id: backup.id ?? uuidv4() } - if (find(this.teamConfig.backups, { id: newBackup.id })) { - throw new Error(`Backup[${newBackup.id}] already exists.`) + if (find(this.teamConfig.backups, { name: newBackup.name })) { + throw new AlreadyExists(`Backup[${newBackup.name}] already exists.`) } this.teamConfig.backups.push(newBackup) return newBackup @@ -213,17 +219,17 @@ export class TeamConfigService { } public getBackups(): Backup[] { - return this.teamConfig.backups + return this.teamConfig.backups ?? [] } public updateBackup(id: string, updates: Partial): Backup { const backup = find(this.teamConfig.backups, { id }) - if (!backup) throw new Error(`Backup[${id}] does not exist.`) + if (!backup) throw new NotExistError(`Backup[${id}] does not exist.`) return merge(backup, updates) } - public deleteBackup(id: string): void { - remove(this.teamConfig.backups, { id }) + public deleteBackup(name: string): void { + remove(this.teamConfig.backups, { name }) } // ===================================== @@ -231,9 +237,10 @@ export class TeamConfigService { // ===================================== public createProject(project: Project): Project { + this.teamConfig.projects ??= [] const newProject = { ...project, id: project.id ?? uuidv4() } - if (find(this.teamConfig.projects, { id: newProject.id })) { - throw new Error(`Project[${newProject.id}] already exists.`) + if (find(this.teamConfig.projects, { name: newProject.name })) { + throw new AlreadyExists(`Project[${newProject.name}] already exists.`) } this.teamConfig.projects.push(newProject) return newProject @@ -248,17 +255,17 @@ export class TeamConfigService { } public getProjects(): Project[] { - return this.teamConfig.projects + return this.teamConfig.projects ?? [] } public updateProject(id: string, updates: Partial): Project { const project = find(this.teamConfig.projects, { id }) - if (!project) throw new Error(`Project[${id}] does not exist.`) + if (!project) throw new NotExistError(`Project[${id}] does not exist.`) return merge(project, updates) } - public deleteProject(id: string): void { - remove(this.teamConfig.projects, { id }) + public deleteProject(name: string): void { + remove(this.teamConfig.projects, { name }) } // ===================================== @@ -266,9 +273,10 @@ export class TeamConfigService { // ===================================== public createNetpol(netpol: Netpol): Netpol { + this.teamConfig.netpols ??= [] const newNetpol = { ...netpol, id: netpol.id ?? uuidv4() } - if (find(this.teamConfig.netpols, { id: newNetpol.id })) { - throw new Error(`Netpol[${newNetpol.id}] already exists.`) + if (find(this.teamConfig.netpols, { name: newNetpol.name })) { + throw new AlreadyExists(`Netpol[${newNetpol.name}] already exists.`) } this.teamConfig.netpols.push(newNetpol) return newNetpol @@ -283,19 +291,19 @@ export class TeamConfigService { } public getNetpols(): Netpol[] { - return this.teamConfig.netpols + return this.teamConfig.netpols ?? [] } public updateNetpol(id: string, updates: Partial): Netpol { const netpol = find(this.teamConfig.netpols, { id }) if (!netpol) { - throw new Error(`Netpol[${id}] does not exist.`) + throw new NotExistError(`Netpol[${id}] does not exist.`) } return merge(netpol, updates) } - public deleteNetpol(id: string): void { - remove(this.teamConfig.netpols, { id }) + public deleteNetpol(name: string): void { + remove(this.teamConfig.netpols, { name }) } // ===================================== @@ -318,9 +326,10 @@ export class TeamConfigService { // ===================================== public createApp(app: App): App { + this.teamConfig.apps ??= [] const newApp = { ...app, id: app.id ?? uuidv4() } if (find(this.teamConfig.apps, { id: newApp.id })) { - throw new Error(`App[${app.id}] already exists.`) + throw new AlreadyExists(`App[${app.id}] already exists.`) } this.teamConfig.apps.push(newApp) return newApp @@ -329,23 +338,13 @@ export class TeamConfigService { public getApp(id: string): App { const app = find(this.teamConfig.apps, { id }) if (!app) { - throw new Error(`App[${id}] does not exist.`) + throw new NotExistError(`App[${id}] does not exist.`) } return app } public getApps(): App[] { - return this.teamConfig.apps - } - - public updateApp(id: string, updates: Partial): App { - const app = find(this.teamConfig.apps, { id }) - if (!app) throw new Error(`App[${id}] does not exist.`) - return merge(app, updates) - } - - public deleteApp(id: string): void { - remove(this.teamConfig.apps, { id }) + return this.teamConfig.apps ?? [] } // ===================================== @@ -369,9 +368,9 @@ export class TeamConfigService { public doesProjectNameExist(name: string): boolean { return ( - this.teamConfig.builds.some((build) => build.name === name) || - this.teamConfig.workloads.some((workload) => workload.name === name) || - this.teamConfig.services.some((service) => service.name === name) + (this.teamConfig.builds && this.teamConfig.builds.some((build) => build.name === name)) || + (this.teamConfig.workloads && this.teamConfig.workloads.some((workload) => workload.name === name)) || + (this.teamConfig.services && this.teamConfig.services.some((service) => service.name === name)) ) } @@ -385,9 +384,6 @@ export class TeamConfigService { /** Update a collection dynamically in the Teamconfig */ public updateCollection(collectionId: string, data: any): void { - if (!has(this.teamConfig, collectionId)) { - throw new Error(`Updating TeamConfig collection [${collectionId}] does not exist.`) - } merge(this.teamConfig[collectionId], data) } } diff --git a/src/utils.ts b/src/utils.ts index 0c589858a..e53f09e43 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -10,6 +10,7 @@ import { BASEURL } from './constants' export function arrayToObject(array: [] = [], keyName = 'name', keyValue = 'value'): Record { const obj = {} + if (!Array.isArray(array)) return array array.forEach((item) => { const cloneItem = cloneDeep(item) obj[cloneItem[keyName]] = cloneItem[keyValue] From 3db73c03f01299b1f28f70076a19254a3da8e7db Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 20 Feb 2025 16:41:23 +0100 Subject: [PATCH 04/49] feat: save apps correctly --- src/otomi-stack.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 8db0e22bb..d2b862313 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1556,10 +1556,11 @@ export default class OtomiStack { } async saveAdminApps(app: App, secretPaths?: string[]): Promise { - const apps = {} const { id, enabled, values, rawValues } = app - apps[id] = { - ...(values || {}), + const apps = { + [id]: { + ...(values || {}), + }, } if (!isEmpty(rawValues)) { apps[id]._rawValues = rawValues From 3c779cb0b9d852621cfc2d5972681630f7dc1972 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 07:59:23 +0100 Subject: [PATCH 05/49] feat: save apps correctly --- src/middleware/authz.ts | 16 +++++++++++++--- src/otomi-stack.ts | 10 ++++------ src/services/TeamConfigService.ts | 5 ++++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/middleware/authz.ts b/src/middleware/authz.ts index e11a486c3..2629a37f1 100644 --- a/src/middleware/authz.ts +++ b/src/middleware/authz.ts @@ -81,13 +81,23 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS Workload: 'workloads', Settings: 'otomi', Project: 'projects', + Netpol: 'netpols', + Policy: 'policies', + SealedSecret: 'sealedSecrets', } - const teamSpecificCollections = ['builds', 'services', 'workloads', 'projects'] // <-- These are fetched per team + const teamSpecificCollections = [ + 'builds', + 'services', + 'workloads', + 'netpols', + 'projects', + 'policies', + 'sealedSecrets', + ] // <-- These are fetched per team const selector = renameKeys(req.params) - console.log(schemaName) const collectionId = schemaToRepoMap[schemaName] - if (['create', 'update'].includes(action)) { + if (collectionId && ['create', 'update'].includes(action)) { let dataOrig = get( req, `apiDoc.components.schemas.TeamSelfService.properties.${schemaName.toLowerCase()}.x-allow-values`, diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index d2b862313..9d235b634 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -521,9 +521,7 @@ export default class OtomiStack { } const teamConfig = this.repoService.createTeamConfig(teamName, data) - console.log('teamConfig', teamConfig) const team = teamConfig.settings - console.log('team', team) const apps = getAppList() const core = this.getCore() apps.forEach((appId) => { @@ -631,7 +629,7 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(id, data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], data.teamId) + await this.doDeployment(['netpols'], teamId) return netpol } @@ -1012,7 +1010,7 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).updateBuild(id, data) await this.saveTeamBuild(teamId, build) - await this.doDeployment(['builds'], data.teamId) + await this.doDeployment(['builds'], teamId) return build } @@ -1181,7 +1179,7 @@ export default class OtomiStack { async editWorkload(teamId: string, id: string, data: Workload): Promise { const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(id, data) await this.saveTeamWorkload(teamId, workload) - await this.doDeployment(['workloads'], data.teamId) + await this.doDeployment(['workloads'], teamId) return workload } @@ -1243,7 +1241,7 @@ export default class OtomiStack { async editService(teamId: string, id: string, data: Service): Promise { const service = this.repoService.getTeamConfigService(teamId).updateService(id, data) await this.saveTeamService(teamId, service) - await this.doDeployment(['services'], data.teamId) + await this.doDeployment(['services'], teamId) return service } diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index ea745ff60..443d03a79 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -100,7 +100,10 @@ export class TeamConfigService { public createWorkloadValues(workloadValues: WorkloadValues): WorkloadValues { this.teamConfig.workloadValues ??= [] const newWorkloadValues = { ...workloadValues, id: workloadValues.id ?? uuidv4() } - if (find(this.teamConfig.workloadValues, { name: newWorkloadValues.name })) { + if ( + find(this.teamConfig.workloadValues, { name: newWorkloadValues.name }) || + find(this.teamConfig.workloadValues, { id: newWorkloadValues.id }) + ) { throw new AlreadyExists(`WorkloadValues[${newWorkloadValues.name}] already exists.`) } this.teamConfig.workloadValues.push(newWorkloadValues) From 964b7b6edc9497e768f0aa24bb43cee50bef6bd2 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 08:06:39 +0100 Subject: [PATCH 06/49] feat: remove unused code --- src/services/RepoService.ts | 179 ------------------------------ src/services/TeamConfigService.ts | 13 ++- 2 files changed, 12 insertions(+), 180 deletions(-) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 4f5d26a5c..979beff4b 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -63,10 +63,6 @@ export class RepoService { return this.teamConfigServiceCache.get(teamId)! } - // ===================================== - // == APPS CRUD (Dictionary) == - // ===================================== - public getApp(id: string): App { const app = find(this.repo.apps, { id }) if (!app) { @@ -91,10 +87,6 @@ export class RepoService { remove(this.repo.apps, { id }) } - // ===================================== - // == USERS CRUD (Array) == - // ===================================== - public createUser(user: User): User { const newUser = { ...user, id: user.id ?? uuidv4() } if (find(this.repo.users, { email: newUser.email })) { @@ -130,9 +122,6 @@ export class RepoService { remove(this.repo.users, { id }) } - // ===================================== - // == TEAM CONFIG CRUD (Dictionary) == - // ===================================== private getDefaultTeamConfig(): TeamConfig { return { builds: [], @@ -164,13 +153,6 @@ export class RepoService { return this.repo.teamConfig[teamId] } - public updateTeamConfig(teamId: string, updates: Partial): void { - if (!has(this.repo.teamConfig, teamId)) { - throw new Error(`TeamConfig[${teamId}] does not exist.`) - } - merge(this.repo.teamConfig[teamId], updates) - } - public deleteTeamConfig(teamId: string): void { if (!has(this.repo.teamConfig, teamId)) { throw new Error(`TeamConfig[${teamId}] does not exist.`) @@ -178,194 +160,33 @@ export class RepoService { delete this.repo.teamConfig[teamId] } - // ===================================== - // == SINGLE OBJECT CRUD (alerts, cluster, dns, etc.) == - // ===================================== - - public getAlerts(): Alerts { - return this.repo.alerts - } - - public updateAlerts(updates: Partial): void { - merge(this.repo.alerts, updates) - } - public getCluster(): Cluster { return this.repo.cluster } - public updateCluster(updates: Partial): void { - merge(this.repo.cluster, updates) - } - public getDns(): Dns { return this.repo.dns } - public updateDns(updates: Partial): void { - merge(this.repo.dns, updates) - } - public getIngress(): Ingress { return this.repo.ingress } - - public updateIngress(updates: Partial): void { - merge(this.repo.ingress, updates) - } - - public getKms(): Kms { - return this.repo.kms - } - - public updateKms(updates: Partial): void { - if (!this.repo.kms) { - throw new Error(`KMS object does not exist.`) - } - merge(this.repo.kms, updates) - } - - public getOidc(): Oidc { - return this.repo.oidc - } - - public updateOidc(updates: Partial): void { - merge(this.repo.oidc, updates) - } - public getOtomi(): Otomi { return this.repo.otomi } - public updateOtomi(updates: Partial): void { - merge(this.repo.otomi, updates) - } - public getSmtp(): Smtp { return this.repo.smtp } - public updateSmtp(updates: Partial): void { - merge(this.repo.smtp, updates) - } - - // ===================================== - // == OTHER DICTIONARIES (databases, obj, versions, etc.) == - // ===================================== - - public createDatabase(key: string, database: any) { - if (has(this.repo.databases, key)) { - throw new Error(`Database[${key}] already exists.`) - } - const newDatabase = { ...database, id: database.id ?? uuidv4() } - - this.repo.databases[key] = newDatabase - return newDatabase - } - - public getDatabase(key: string): any | undefined { - return this.repo.databases[key] - } - - public updateDatabase(key: string, updates: any): void { - if (!has(this.repo.databases, key)) { - throw new Error(`Database[${key}] does not exist.`) - } - merge(this.repo.databases[key], updates) - } - - public deleteDatabase(key: string): void { - if (!has(this.repo.databases, key)) { - throw new Error(`Database[${key}] does not exist.`) - } - delete this.repo.databases[key] - } - - public createVersion(key: string, version: any) { - if (has(this.repo.versions, key)) { - throw new Error(`Version[${key}] already exists.`) - } - const newVersion = { ...version, id: version.id ?? uuidv4() } - this.repo.versions[key] = newVersion - return newVersion - } - - public getVersion(key: string): any | undefined { - return this.repo.versions[key] - } - - public updateVersion(key: string, updates: any): void { - if (!has(this.repo.versions, key)) { - throw new Error(`Version[${key}] does not exist.`) - } - merge(this.repo.versions[key], updates) - } - - public deleteVersion(key: string): void { - if (!has(this.repo.versions, key)) { - throw new Error(`Version[${key}] does not exist.`) - } - delete this.repo.versions[key] - } - - public createObj(key: string, obj: any) { - if (has(this.repo.obj, key)) { - throw new Error(`Obj[${key}] already exists.`) - } - const newObj = { ...obj, id: obj.id ?? uuidv4() } - this.repo.obj[key] = newObj - return newObj - } - public getObj(): any | undefined { return this.repo.obj } - public updateObj(key: string, updates: any): void { - if (!has(this.repo.obj, key)) { - throw new Error(`Obj[${key}] does not exist.`) - } - merge(this.repo.obj[key], updates) - } - - public deleteObj(key: string): void { - if (!has(this.repo.obj, key)) { - throw new Error(`Obj[${key}] does not exist.`) - } - delete this.repo.obj[key] - } - - public createPlatformBackups(key: string, platformBackup: any) { - if (has(this.repo.platformBackups, key)) { - throw new Error(`PlatformBackups[${key}] already exists.`) - } - const newBackup = { ...platformBackup, id: platformBackup.id ?? uuidv4() } - this.repo.platformBackups[key] = newBackup - return newBackup - } - public getPlatformBackups(): any | undefined { return this.repo.platformBackups } - public getPlatformBackup(key: string): any | undefined { - return this.repo.platformBackups[key] - } - - public updatePlatformBackups(key: string, updates: any): void { - if (!has(this.repo.platformBackups, key)) { - throw new Error(`PlatformBackups[${key}] does not exist.`) - } - merge(this.repo.platformBackups[key], updates) - } - - public deletePlatformBackups(key: string): void { - if (!has(this.repo.platformBackups, key)) { - throw new Error(`Obj[${key}] does not exist.`) - } - delete this.repo.platformBackups[key] - } - public getSettings(): Settings { return { alerts: this.repo.alerts, diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 443d03a79..97b605d12 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -18,7 +18,18 @@ import { AlreadyExists, NotExistError } from '../error' import { v4 as uuidv4 } from 'uuid' export class TeamConfigService { - constructor(private teamConfig: TeamConfig) {} + constructor(private teamConfig: TeamConfig) { + this.teamConfig.builds ??= [] + this.teamConfig.workloads ??= [] + this.teamConfig.workloadValues ??= [] + this.teamConfig.services ??= [] + this.teamConfig.sealedSecrets ??= [] + this.teamConfig.backups ??= [] + this.teamConfig.projects ??= [] + this.teamConfig.netpols ??= [] + this.teamConfig.apps ??= [] + this.teamConfig.policies ??= {} + } // ===================================== // == BUILDS CRUD == From 0ad5d745191c997061c1814aeb3c32e01fd4a936 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 11:09:06 +0100 Subject: [PATCH 07/49] feat: clean up code --- src/git.ts | 33 ++-- src/otomi-stack.ts | 242 ++++++++++-------------------- src/services/RepoService.ts | 4 +- src/services/TeamConfigService.ts | 28 ++-- 4 files changed, 121 insertions(+), 186 deletions(-) diff --git a/src/git.ts b/src/git.ts index 9295156b5..d818b6b5b 100644 --- a/src/git.ts +++ b/src/git.ts @@ -211,7 +211,11 @@ export class Git { return merge(data, secretData) as Core } - async saveConfig(config: Record, fileMap: FileMap): Promise> { + async saveConfig( + config: Record, + fileMap: FileMap, + unsetBlankAttributes?: boolean, + ): Promise> { const jsonPathsValuesPublic = jsonpath.nodes(config, fileMap.jsonPathExpression) await Promise.all( jsonPathsValuesPublic.map(async (node) => { @@ -220,7 +224,7 @@ export class Git { try { const filePath = getFilePath(fileMap, nodePath, nodeValue, '') const manifest = renderManifest(fileMap, nodePath, nodeValue) - await this.writeFile(filePath, manifest) + await this.writeFile(filePath, manifest, unsetBlankAttributes) } catch (e) { console.log(nodePath) console.log(fileMap) @@ -245,15 +249,20 @@ export class Git { } }) - const jsonPathsValuesPublic = jsonpath.nodes(plainData, fileMap.jsonPathExpression) + await this.saveConfig(plainData, fileMap) + await this.saveSecretConfig(secretData, fileMap) + } + + async saveSecretConfig(secretConfig: Record, fileMap: FileMap, unsetBlankAttributes?: boolean) { + const jsonPathsValuesSecrets = jsonpath.nodes(secretConfig, fileMap.jsonPathExpression) await Promise.all( - jsonPathsValuesPublic.map(async (node) => { + jsonPathsValuesSecrets.map(async (node) => { const nodePath = node.path const nodeValue = node.value try { - const filePath = getFilePath(fileMap, nodePath, nodeValue, '') - const manifest = renderManifest(fileMap, nodePath, nodeValue) - await this.writeFile(filePath, manifest) + const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') + const manifest = renderManifestForSecrets(fileMap, nodeValue) + await this.writeFile(filePath, manifest, unsetBlankAttributes) } catch (e) { console.log(nodePath) console.log(fileMap) @@ -261,15 +270,17 @@ export class Git { } }), ) - const jsonPathsValuesSecrets = jsonpath.nodes(secretData, fileMap.jsonPathExpression) + } + + async deleteConfig(config: Record, fileMap: FileMap, fileNamePrefix = '') { + const jsonPathsValuesSecrets = jsonpath.nodes(config, fileMap.jsonPathExpression) await Promise.all( jsonPathsValuesSecrets.map(async (node) => { const nodePath = node.path const nodeValue = node.value try { - const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') - const manifest = renderManifestForSecrets(fileMap, nodeValue) - await this.writeFile(filePath, manifest) + const filePath = getFilePath(fileMap, nodePath, nodeValue, fileNamePrefix) + await this.removeFile(filePath) } catch (e) { console.log(nodePath) console.log(fileMap) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 9d235b634..e317eaa0c 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -82,9 +82,8 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' -import { getFileMaps, getFilePath, loadValues, renderManifestForSecrets } from './repo' +import { getFileMaps, loadValues } from './repo' import { RepoService } from './services/RepoService' -import jsonpath from 'jsonpath' interface ExcludedApp extends App { managed: boolean @@ -538,6 +537,7 @@ export default class OtomiStack { } if (deploy) { await this.saveTeam(team) + //TODO do this better for the teamconfig await this.doDeployment(['teamConfig']) } return team @@ -595,8 +595,7 @@ export default class OtomiStack { } async deleteBackup(teamId: string, id: string): Promise { - const backup = this.repoService.getTeamConfigService(teamId).getBackup(id) - await this.deleteTeamBackup(teamId, backup.name) + await this.deleteTeamBackup(teamId, id) await this.doDeployment(['backups'], teamId) } @@ -861,19 +860,19 @@ export default class OtomiStack { // Deletes a project and all its related resources async deleteProject(teamId: string, id: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProject(id) - if (p.build?.name) { - await this.deleteTeamBuild(teamId, p.build.name) + if (p.build?.id) { + await this.deleteTeamBuild(teamId, p.build.id) } - if (p.workload?.name) { - await this.deleteTeamWorkload(teamId, p.workload.name) + if (p.workload?.id) { + await this.deleteTeamWorkload(teamId, p.workload.id) } - if (p.workloadValues?.name) { - await this.deleteTeamWorkloadValues(teamId, p.workloadValues.name) + if (p.workloadValues?.id) { + await this.deleteTeamWorkloadValues(teamId, p.workloadValues.id) } - if (p.service?.name) { - await this.deleteTeamService(teamId, p.service.name) + if (p.service?.id) { + await this.deleteTeamService(teamId, p.service.id) } - await this.deleteTeamProject(teamId, p.name) + await this.deleteTeamProject(teamId, id) await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } @@ -1016,14 +1015,13 @@ export default class OtomiStack { async deleteBuild(teamId: string, id: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProjects() - const build = this.repoService.getTeamConfigService(teamId).getBuild(id) p.forEach((project: Project) => { if (project?.build?.id === id) { const updatedData = { ...project, build: undefined } this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) - await this.deleteTeamBuild(teamId, build.name) + await this.deleteTeamBuild(teamId, id) await this.doDeployment(['builds'], teamId) } @@ -1191,10 +1189,8 @@ export default class OtomiStack { this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) } }) - const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) - await this.deleteTeamWorkloadValues(teamId, workloadValues.name!) - const workload = this.repoService.getTeamConfigService(teamId).getWorkload(id) - await this.deleteTeamWorkload(teamId, workload.name) + await this.deleteTeamWorkloadValues(teamId, id) + await this.deleteTeamWorkload(teamId, id) await this.doDeployment(['workloads', 'workloadValues'], teamId) } @@ -1255,8 +1251,7 @@ export default class OtomiStack { } }) } - const service = this.repoService.getTeamConfigService(teamId).getService(id) - await this.deleteTeamService(teamId, service.name) + await this.deleteTeamService(teamId, id) await this.doDeployment(['services'], teamId) } @@ -1588,23 +1583,7 @@ export default class OtomiStack { const users: User[] = [] users.push(user) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplUser')! - const jsonPathsValuesSecrets = jsonpath.nodes({ users }, fileMap.jsonPathExpression) - //TODO clean this up - await Promise.all( - jsonPathsValuesSecrets.map(async (node) => { - const nodePath = node.path - const nodeValue = node.value - try { - const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') - const manifest = renderManifestForSecrets(fileMap, nodeValue) - await this.git.writeFile(filePath, manifest, false) - } catch (e) { - console.log(nodePath) - console.log(fileMap) - throw e - } - }), - ) + await this.git.saveSecretConfig({ users }, fileMap, false) } async deleteUserFile(user: User): Promise { @@ -1613,32 +1592,12 @@ export default class OtomiStack { const users: User[] = [] users.push(user) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplUser')! - const jsonPathsValuesSecrets = jsonpath.nodes({ users }, fileMap.jsonPathExpression) - await Promise.all( - jsonPathsValuesSecrets.map(async (node) => { - const nodePath = node.path - const nodeValue = node.value - try { - const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') - await this.git.removeFile(filePath) - } catch (e) { - console.log(nodePath) - console.log(fileMap) - throw e - } - }), - ) + await this.git.deleteConfig({ users }, fileMap, 'secrets.') } async saveTeam(team: Team, secretPaths?: string[]): Promise { debug(`Saving team ${team.name}`) - const repo = { - teamConfig: { - [team.name]: { - settings: team, - }, - }, - } + const repo = this.createTeamConfigInRepo(team.name, 'settings', team) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSettingSet')! await this.git.saveConfigWithSecrets(repo, secretPaths ?? this.getSecretPaths(), fileMap) } @@ -1651,118 +1610,87 @@ export default class OtomiStack { async saveTeamSealedSecrets(teamId: string, sealedSecret: SealedSecret): Promise { debug(`Saving sealed secret ${sealedSecret.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - sealedSecrets: [sealedSecret], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'sealedSecrets', sealedSecret) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamSealedSecrets(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) + async deleteTeamSealedSecrets(teamId: string, id: string): Promise { + const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(id) + this.repoService.getTeamConfigService(teamId).deleteSealedSecret(id) + const repo = this.createTeamConfigInRepo(teamId, 'sealedSecrets', sealedSecret) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamBackup(teamId: string, backup: Backup): Promise { debug(`Saving backup ${backup.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - backups: [backup], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'backups', backup) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamBackup(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteBackup(name) + async deleteTeamBackup(teamId: string, id: string): Promise { + const backup = this.repoService.getTeamConfigService(teamId).getBackup(id) + this.repoService.getTeamConfigService(teamId).deleteBackup(id) + const repo = this.createTeamConfigInRepo(teamId, 'backups', backup) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamNetpols(teamId: string, netpol: Netpol): Promise { debug(`Saving netpols ${netpol.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - netpols: [netpol], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'netpols', netpol) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamNetpol(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteNetpol(name) + async deleteTeamNetpol(teamId: string, id: string): Promise { + const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(id) + this.repoService.getTeamConfigService(teamId).deleteNetpol(id) + const repo = this.createTeamConfigInRepo(teamId, 'netpols', netpol) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamWorkload(teamId: string, workload: Workload): Promise { debug(`Saving workload ${workload.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - workloads: [workload], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'workloads', workload) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamWorkload(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteWorkload(name) + async deleteTeamWorkload(teamId: string, id: string): Promise { + const workload = this.repoService.getTeamConfigService(teamId).getWorkload(id) + this.repoService.getTeamConfigService(teamId).deleteWorkload(id) + const repo = this.createTeamConfigInRepo(teamId, 'workloads', workload) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamProject(teamId: string, project: Project): Promise { debug(`Saving project ${project.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - projects: [project], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'projects', project) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamProject(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteProject(name) + async deleteTeamProject(teamId: string, id: string): Promise { + const project = this.repoService.getTeamConfigService(teamId).getProject(id) + this.repoService.getTeamConfigService(teamId).deleteProject(id) + const repo = this.createTeamConfigInRepo(teamId, 'projects', project) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamBuild(teamId: string, build: Build): Promise { debug(`Saving build ${build.name} for team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - builds: [build], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'builds', build) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! await this.git.saveConfig(repo, fileMap) } @@ -1778,25 +1706,20 @@ export default class OtomiStack { await this.repo.writeFile(relativePath, outData) } + async deleteTeamBuild(teamId: string, id: string): Promise { + const build = this.repoService.getTeamConfigService(teamId).getBuild(id) + this.repoService.getTeamConfigService(teamId).deleteBuild(id) - async deleteTeamBuild(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteBuild(name) - + const repo = this.createTeamConfigInRepo(teamId, 'builds', build) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamPolicies(teamId: string): Promise { + debug(`Saving team policies ${teamId}`) const policies = this.getTeamPolicies(teamId) - debug(`Saving team ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - policies, - }, - }, - } + + const repo = this.createTeamConfigInRepo(teamId, 'policies', policies) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamPolicy')! await this.git.saveConfig(repo, fileMap) } @@ -1804,46 +1727,47 @@ export default class OtomiStack { async saveTeamWorkloadValues(teamId: string, workloadValues: WorkloadValues): Promise { debug(`Saving workload values: ${workloadValues.id!} teamId: ${teamId} name: ${workloadValues.name}`) const data = this.getWorkloadValues(teamId, workloadValues.id!) - const outData = cloneDeep(data) as Record - outData.values = stringifyYaml(data.values, undefined, 4) - const repo = { - teamConfig: { - [teamId]: { - workloadValues: [outData], - }, - }, - } + const updatedWorkloadValues = cloneDeep(data) as Record + updatedWorkloadValues.values = stringifyYaml(data.values, undefined, 4) + + const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', updatedWorkloadValues) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamWorkloadValues(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(name) + async deleteTeamWorkloadValues(teamId: string, id: string): Promise { + const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) + this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(id) + const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', workloadValues) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) } async saveTeamService(teamId: string, service: Service): Promise { debug(`Saving service: ${service.name} teamId: ${teamId}`) - const repo = { - teamConfig: { - [teamId]: { - services: [service], - }, - }, - } + const repo = this.createTeamConfigInRepo(teamId, 'services', service) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamService(teamId: string, name: string): Promise { - this.repoService.getTeamConfigService(teamId).deleteService(name) + async deleteTeamService(teamId: string, id: string): Promise { + const service = this.repoService.getTeamConfigService(teamId).getService(id) + this.repoService.getTeamConfigService(teamId).deleteService(id) + const repo = this.createTeamConfigInRepo(teamId, 'services', service) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! - const filePath = fileMap.pathGlob.replace('*', teamId).replace('*', name) - await this.git.removeFile(filePath) + await this.git.deleteConfig(repo, fileMap) + } + + private createTeamConfigInRepo(teamId: string, key: string, value: T): Record { + return { + teamConfig: { + [teamId]: { + [key]: Array.isArray(value) ? value : [value], + }, + }, + } } async getSession(user: k8s.User): Promise { diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 979beff4b..32e8cfc7b 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -22,7 +22,7 @@ import { Workload, } from '../otomi-models' import { TeamConfigService } from './TeamConfigService' -import { find, flatMap, has, map, mapValues, merge, remove } from 'lodash' +import { find, flatMap, has, map, mapValues, merge, remove, set } from 'lodash' import { v4 as uuidv4 } from 'uuid' import { AlreadyExists } from '../error' @@ -259,6 +259,6 @@ export class RepoService { if (!has(this.repo, collectionId)) { throw new Error(`Updating repo collection [${collectionId}] does not exist.`) } - merge(this.repo[collectionId], data) + set(this.repo, collectionId, data) } } diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 97b605d12..d6042348b 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -13,7 +13,7 @@ import { Workload, WorkloadValues, } from '../otomi-models' -import { find, has, merge, remove } from 'lodash' +import { find, has, merge, remove, set } from 'lodash' import { AlreadyExists, NotExistError } from '../error' import { v4 as uuidv4 } from 'uuid' @@ -63,8 +63,8 @@ export class TeamConfigService { return merge(build, updates) } - public deleteBuild(name: string): void { - remove(this.teamConfig.builds, { name }) + public deleteBuild(id: string): void { + remove(this.teamConfig.builds, { id }) } // ===================================== @@ -100,8 +100,8 @@ export class TeamConfigService { return merge(workload, updates) } - public deleteWorkload(name: string): void { - remove(this.teamConfig.workloads, { name }) + public deleteWorkload(id: string): void { + remove(this.teamConfig.workloads, { id }) } // ===================================== @@ -135,8 +135,8 @@ export class TeamConfigService { return merge(workloadValues, updates) } - public deleteWorkloadValues(name: string): void { - remove(this.teamConfig.workloadValues, { name }) + public deleteWorkloadValues(id: string): void { + remove(this.teamConfig.workloadValues, { id }) } // ===================================== @@ -206,8 +206,8 @@ export class TeamConfigService { return merge(secret, updates) } - public deleteSealedSecret(name: string): void { - remove(this.teamConfig.sealedSecrets, { name }) + public deleteSealedSecret(id: string): void { + remove(this.teamConfig.sealedSecrets, { id }) } // ===================================== @@ -242,8 +242,8 @@ export class TeamConfigService { return merge(backup, updates) } - public deleteBackup(name: string): void { - remove(this.teamConfig.backups, { name }) + public deleteBackup(id: string): void { + remove(this.teamConfig.backups, { id }) } // ===================================== @@ -278,8 +278,8 @@ export class TeamConfigService { return merge(project, updates) } - public deleteProject(name: string): void { - remove(this.teamConfig.projects, { name }) + public deleteProject(id: string): void { + remove(this.teamConfig.projects, { id }) } // ===================================== @@ -398,6 +398,6 @@ export class TeamConfigService { /** Update a collection dynamically in the Teamconfig */ public updateCollection(collectionId: string, data: any): void { - merge(this.teamConfig[collectionId], data) + set(this.teamConfig, collectionId, data) } } From a4f68251bb2a6014c5d4fc59a9e0f89e525d4ed7 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 11:50:59 +0100 Subject: [PATCH 08/49] feat: fix backups --- src/otomi-stack.ts | 2 +- src/services/RepoService.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index e317eaa0c..15064cacd 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -564,7 +564,7 @@ export default class OtomiStack { } getAllBackups(): Array { - return this.repoService.getPlatformBackups() + return this.repoService.getAllBackups() } async createBackup(teamId: string, data: Backup): Promise { diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 32e8cfc7b..8a2087139 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -246,6 +246,10 @@ export class RepoService { return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) ?? [] } + public getAllBackups(): SealedSecret[] { + return flatMap(this.repo.teamConfig, 'backups').filter(Boolean) ?? [] + } + /** Retrieve a collection dynamically from the Repo */ public getCollection(collectionId: string): any { if (!has(this.repo, collectionId)) { From f67450649905fcb9b6765a774f6037b67d49b9b3 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 12:01:53 +0100 Subject: [PATCH 09/49] feat: fix backups --- src/services/RepoService.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 8a2087139..3dafa26fe 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -1,6 +1,7 @@ import { Alerts, App, + Backup, Build, Cluster, Dns, @@ -246,7 +247,7 @@ export class RepoService { return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) ?? [] } - public getAllBackups(): SealedSecret[] { + public getAllBackups(): Backup[] { return flatMap(this.repo.teamConfig, 'backups').filter(Boolean) ?? [] } From 21cc2c6b2869c03d48b64fd4f2b374ab68644d46 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 21 Feb 2025 17:02:38 +0100 Subject: [PATCH 10/49] feat: make sealedsecrets compatible with new structure --- src/git.ts | 10 +++++++++- src/otomi-stack.ts | 36 +++++++++++++++++++++------------- src/utils/sealedSecretUtils.ts | 9 ++++++--- 3 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/git.ts b/src/git.ts index d818b6b5b..c66de7dbe 100644 --- a/src/git.ts +++ b/src/git.ts @@ -5,7 +5,7 @@ import { copy, ensureDir, pathExists, readFile, writeFile } from 'fs-extra' import { unlink } from 'fs/promises' import stringifyJson from 'json-stable-stringify' import { cloneDeep, get, isEmpty, merge, set, unset } from 'lodash' -import { dirname, join } from 'path' +import { basename, dirname, join } from 'path' import simpleGit, { CheckRepoActions, CleanOptions, CommitResult, ResetMode, SimpleGit } from 'simple-git' import { cleanEnv, GIT_BRANCH, GIT_LOCAL_PATH, GIT_PASSWORD, GIT_REPO_URL, GIT_USER, TOOLS_HOST } from 'src/validators' import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' @@ -17,6 +17,7 @@ import { removeBlankAttributes } from './utils' import { FileMap, getFilePath, renderManifest, renderManifestForSecrets } from './repo' import jsonpath from 'jsonpath' import { rmSync } from 'fs' +import { glob } from 'glob' const debug = Debug('otomi:repo') @@ -196,6 +197,13 @@ export class Git { return await pathExists(absolutePath) } + async readDir(relativePath: string): Promise { + const absolutePath = join(this.path, relativePath) + const files = await glob([`${absolutePath}/**/*.yaml`]) + const filenames = files.map((file) => basename(file)) + return filenames + } + async readFile(file: string, checkSuffix = false): Promise> { if (!(await this.fileExists(file))) return {} const safeFile = checkSuffix ? this.getSafePath(file) : file diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 15064cacd..07b41ba9a 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -109,6 +109,13 @@ const env = cleanEnv({ export const rootPath = '/tmp/otomi/values' +export function getTeamSealedSecretsValuesRootPath(teamId: string): string { + return `env/teams/${teamId}/sealedsecrets` +} +export function getTeamSealedSecretsValuesFilePath(teamId: string, sealedSecretsName: string): string { + return `env/teams/${teamId}/sealedsecrets/${sealedSecretsName}` +} + export default class OtomiStack { private coreValues: Core editor?: string @@ -1473,7 +1480,7 @@ export default class OtomiStack { const encryptedItem = encryptSecretItem(certificate, data.name, namespace, obj.value, 'namespace-wide') return { [obj.key]: encryptedItem } }) - const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord + const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord[] const sealedSecret = this.repoService .getTeamConfigService(teamId) .createSealedSecret({ ...data, teamId, encryptedData, namespace }) @@ -1500,20 +1507,22 @@ export default class OtomiStack { const encryptedItem = encryptSecretItem(certificate, data.name, namespace, obj.value, 'namespace-wide') return { [obj.key]: encryptedItem } }) - const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord - const sealedSecret = this.db.updateItem('sealedsecrets', { ...data, encryptedData }, { id }) as SealedSecret + const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord[] + const sealedSecret = this.repoService + .getTeamConfigService(data.teamId!) + .createSealedSecret({ ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, id) - await this.doDeployment(['sealedsecrets']) + await this.doDeployment(['sealedsecrets'], data.teamId) return sealedSecret } - async deleteSealedSecret(id: string): Promise { - const sealedSecret = await this.getSealedSecret(id) - this.db.deleteItem('sealedsecrets', { id }) + async deleteSealedSecret(teamId: string, id: string): Promise { + const sealedSecret = await this.getSealedSecret(teamId, id) + this.repoService.getTeamConfigService(teamId).deleteSealedSecret(id) const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${id}.yaml`) - await this.repo.removeFile(relativePath) - await this.doDeployment(['sealedsecrets']) + await this.git.removeFile(relativePath) + await this.doDeployment(['sealedsecrets'], teamId) } async getSealedSecret(teamId: string, id: string): Promise { @@ -1608,11 +1617,10 @@ export default class OtomiStack { await this.git.removeDir(teamDir) } - async saveTeamSealedSecrets(teamId: string, sealedSecret: SealedSecret): Promise { - debug(`Saving sealed secret ${sealedSecret.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'sealedSecrets', sealedSecret) - const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! - await this.git.saveConfig(repo, fileMap) + async saveTeamSealedSecrets(teamId: string, data: any, id: string): Promise { + const relativePath = getTeamSealedSecretsValuesFilePath(teamId, `${id}.yaml`) + debug(`Saving sealed secrets of team: ${teamId}`) + await this.git.writeFile(relativePath, data) } async deleteTeamSealedSecrets(teamId: string, id: string): Promise { diff --git a/src/utils/sealedSecretUtils.ts b/src/utils/sealedSecretUtils.ts index 4c16b080d..4b703494d 100644 --- a/src/utils/sealedSecretUtils.ts +++ b/src/utils/sealedSecretUtils.ts @@ -41,7 +41,10 @@ export function encryptSecretItem(certificate, secretName, ns, data, scope) { return out } -export type EncryptedDataRecord = Record +export interface EncryptedDataRecord { + key: string + value: string +} export interface SealedSecretManifestType { apiVersion: string @@ -54,7 +57,7 @@ export interface SealedSecretManifestType { labels?: Record } spec: { - encryptedData: EncryptedDataRecord + encryptedData: EncryptedDataRecord[] template: { type: | 'kubernetes.io/opaque' @@ -75,7 +78,7 @@ export interface SealedSecretManifestType { export function sealedSecretManifest( data: SealedSecret, - encryptedData: EncryptedDataRecord, + encryptedData: EncryptedDataRecord[], namespace: string, ): SealedSecretManifestType { const annotations = data.metadata?.annotations?.reduce((acc, item) => { From f9f5382be069428b12271a8b046ad735fc534fad Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 24 Feb 2025 11:46:34 +0100 Subject: [PATCH 11/49] fix: unit tests --- jest.config.ts | 2 +- src/api.authz.test.ts | 13 +- src/api/secrets.ts | 19 --- src/api/teams/{teamId}/secrets/{secretId}.ts | 38 ----- src/db.test.ts | 71 -------- src/db.ts | 168 ------------------- src/middleware/jwt.test.ts | 4 + src/otomi-stack.test.ts | 151 +++++++++-------- 8 files changed, 93 insertions(+), 373 deletions(-) delete mode 100644 src/api/secrets.ts delete mode 100644 src/api/teams/{teamId}/secrets/{secretId}.ts delete mode 100644 src/db.test.ts delete mode 100644 src/db.ts diff --git a/jest.config.ts b/jest.config.ts index 0766224de..6b8a60503 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -13,5 +13,5 @@ const config: Config.InitialOptions = { } export default config process.env = Object.assign(process.env, { - NODE_ENV: 'test', + NODE_ENV: 'test' }) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index 903c6492a..05e13f358 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -9,8 +9,9 @@ import request, { SuperAgentTest } from 'supertest' import { HttpError } from './error' import { getSessionStack } from './middleware' import { App, Coderepo, SealedSecret } from './otomi-models' -import { Repo } from './repo' +import { mockDeep } from 'jest-mock-extended' import * as getValuesSchemaModule from './utils' +import { Git } from './git' const platformAdminToken = getToken(['platform-admin']) const teamAdminToken = getToken(['team-admin', 'team-team1']) @@ -39,13 +40,15 @@ describe('API authz tests', () => { beforeAll(async () => { const _otomiStack = await getSessionStack() - _otomiStack.repo = mockDeep() + _otomiStack.git = mockDeep() _otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) + await _otomiStack.initRepo() await _otomiStack.createTeam({ name: 'team1' }) + await _otomiStack.createTeam({ name: 'team2' }) otomiStack = _otomiStack as jest.Mocked otomiStack.createTeam = jest.fn().mockResolvedValue(undefined) - otomiStack.saveTeams = jest.fn().mockResolvedValue(undefined) + otomiStack.saveTeam = jest.fn().mockResolvedValue(undefined) otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) await otomiStack.init() app = await initApp(otomiStack) @@ -482,10 +485,6 @@ describe('API authz tests', () => { .expect(403) }) - test('team member cannot get all secrets', async () => { - await agent.get('/v1/secrets').set('Authorization', `Bearer ${teamMemberToken}`).expect(403) - }) - test('team member cannot get all sealedsecrets', async () => { await agent.get('/v1/sealedsecrets').set('Authorization', `Bearer ${teamMemberToken}`).expect(403) }) diff --git a/src/api/secrets.ts b/src/api/secrets.ts deleted file mode 100644 index b972c21cb..000000000 --- a/src/api/secrets.ts +++ /dev/null @@ -1,19 +0,0 @@ -import Debug from 'debug' -import { Operation, OperationHandlerArray } from 'express-openapi' -import { OpenApiRequestExt } from 'src/otomi-models' - -const debug = Debug('otomi:api:secrets') - -export default function (): OperationHandlerArray { - const get: Operation = [ - ({ otomi }: OpenApiRequestExt, res): void => { - debug('getAllSecrets') - const v = otomi.getAllSecrets() - res.json(v) - }, - ] - const api = { - get, - } - return api -} diff --git a/src/api/teams/{teamId}/secrets/{secretId}.ts b/src/api/teams/{teamId}/secrets/{secretId}.ts deleted file mode 100644 index dfe8dffed..000000000 --- a/src/api/teams/{teamId}/secrets/{secretId}.ts +++ /dev/null @@ -1,38 +0,0 @@ -import Debug from 'debug' -import { Operation, OperationHandlerArray } from 'express-openapi' -import { OpenApiRequestExt, Secret } from 'src/otomi-models' - -const debug = Debug('otomi:api:teams:secrets') - -export default function (): OperationHandlerArray { - const del: Operation = [ - ({ otomi, params: { secretId } }: OpenApiRequestExt, res): void => { - debug(`deleteSecret(${secretId})`) - otomi.deleteSecret(decodeURIComponent(secretId)) - res.json({}) - }, - ] - const get: Operation = [ - ({ otomi, params: { secretId } }: OpenApiRequestExt, res): void => { - debug(`getSecret(${secretId})`) - const data = otomi.getSecret(decodeURIComponent(secretId)) - res.json(data) - }, - ] - const put: Operation = [ - ({ otomi, params: { teamId, secretId }, body }: OpenApiRequestExt, res): void => { - debug(`editSecret(${secretId})`) - const data = otomi.editSecret(decodeURIComponent(secretId), { - ...body, - teamId: decodeURIComponent(teamId), - } as Secret) - res.json(data) - }, - ] - const api = { - delete: del, - get, - put, - } - return api -} diff --git a/src/db.test.ts b/src/db.test.ts deleted file mode 100644 index 95d7efeee..000000000 --- a/src/db.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import Db from 'src/db' -jest.mock('src/otomi-stack') - -describe('Db', () => { - let testDb: Db - - beforeEach(() => { - testDb = new Db() - }) - - test('can store with id', () => { - const v = testDb.createItem('teams', { name: 'n1', k: '1' }, undefined, '1') - expect(v).toEqual({ name: 'n1', k: '1', id: '1' }) - }) - - test('can store without id', () => { - const v = testDb.createItem('teams', { name: 'n1', k: '1' }) - expect(v).toMatchObject({ name: 'n1', k: '1' }) - // @ts-ignore - expect(v.id).toBeDefined() - }) - - test('cannot store resource with existing selector', () => { - testDb.createItem('teams', { name: 'n1' }) - expect(() => testDb.createItem('teams', { name: 'n1' }, { name: 'n1' })).toThrow() - }) - - test('can remove item', () => { - testDb.createItem('teams', { name: 'name1', k: 'a' }, undefined, '1') - testDb.createItem('teams', { name: 'name2', k: 'b' }, undefined, '2') - - testDb.deleteItem('teams', { id: '1' }) - expect(() => testDb.getItem('teams', { id: '1' })).toThrow() - - const v = testDb.getItem('teams', { id: '2' }) - expect(v).not.toBeUndefined() - }) - - test('can update item', () => { - testDb.createItem('teams', { name: 'n1', k: '1' }, undefined, '1') - testDb.updateItem('teams', { name: 'n1', k: '2' }, { id: '1' }) - const v = testDb.getItem('teams', { id: '1' }) - expect(v).toEqual({ name: 'n1', k: '2', id: '1' }) - }) - - test('can obtain item', () => { - testDb.createItem('teams', { name: 'n1', k: '1' }, undefined, '1') - const v = testDb.getItem('teams', { name: 'n1' }) - expect(v).toEqual({ name: 'n1', k: '1', id: '1' }) - }) - - test('can obtain collection', () => { - testDb.createItem('teams', { name: 'n1', k: '1' }) - testDb.createItem('teams', { name: 'n2', k: '1' }) - const v = testDb.getCollection('teams') - expect(v.length).toBe(2) - }) - - test('can obtain collection by selector', () => { - testDb.createItem('teams', { name: 'n1', k: '1' }) - testDb.createItem('teams', { name: 'n2', k: '1' }) - const v = testDb.getCollection('teams', { k: '1' }) - expect(v.length).toBe(2) - const v2 = testDb.getCollection('teams', { name: 'n1' }) - expect(v2.length).toBe(1) - }) - - test('throws error if item does not exist', () => { - expect(() => testDb.getItem('teams', { teamId: 'n1' })).toThrow() - }) -}) diff --git a/src/db.ts b/src/db.ts deleted file mode 100644 index d9a77418f..000000000 --- a/src/db.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { debug } from 'console' -import cloneDeep from 'lodash/cloneDeep' -import findIndex from 'lodash/findIndex' -import low from 'lowdb' -import FileSync from 'lowdb/adapters/FileSync' -import Memory from 'lowdb/adapters/Memory' -import { AlreadyExists, NotExistError } from 'src/error' -import { - App, - Backup, - Build, - Cloudtty, - Cluster, - Coderepo, - Netpol, - Policies, - Project, - SealedSecret, - Secret, - Service, - Settings, - Team, - User, - Workload, - WorkloadValues, -} from 'src/otomi-models' -import { mergeData } from 'src/utils' -import { v4 as uuidv4 } from 'uuid' - -export type DbType = - | Backup - | Build - | Cloudtty - | Cluster - | Netpol - | SealedSecret - | Secret - | Service - | Team - | Settings - | App - | Workload - | WorkloadValues - | User - | Project - | Policies - | Coderepo -export type Schema = { - apps: App[] - sealedsecrets: SealedSecret[] - secrets: Secret[] - services: Service[] - netpols: Netpol[] - settings: Settings - teams: Team[] - workloads: Workload[] - workloadValues: WorkloadValues[] - builds: Build[] - policies: Record - cloudttys: Cloudtty[] - users: User[] - projects: Project[] - coderepos: Coderepo[] -} - -export default class Db { - db: low.LowdbSync - - constructor(path?: string) { - // @ts-ignore - this.db = low(path === undefined ? new Memory('') : new FileSync(path)) - this.db._.mixin({ - replaceRecord(arr: Record[], currentObject: Record, newObject: Record) { - return arr.splice(findIndex(arr, currentObject), 1, newObject) - }, - }) - // Set some defaults (required if your JSON file is empty) - this.db - .defaults({ - apps: [], - backups: [], - builds: [], - cloudttys: [], - netpols: [], - users: [], - projects: [], - coderepos: [], - policies: {}, - sealedsecrets: [], - secrets: [], - services: [], - settings: {}, - teams: [], - workloads: [], - workloadValues: [], - }) - .write() - } - - getItem(name: string, selector: any): DbType { - // By default data is returned by reference, this means that modifications to returned objects may change the database. - // To avoid such behavior, we use .cloneDeep(). - const data = this.getItemReference(name, selector) - return cloneDeep(data) - } - - getItemReference(type: string, selector: any, mustThrow = true): DbType | undefined { - const coll = this.db.get(type) - // @ts-ignore - const data = coll.find(selector).value() - if (data === undefined) { - debug(`Selector props do not exist in '${type}': ${JSON.stringify(selector)}`) - if (mustThrow) throw new NotExistError() - else return - } - if (data?.length) { - debug(`More than one item found for '${type}' with selector: ${JSON.stringify(selector)}`) - if (mustThrow) throw new NotExistError() - else return - } - return data - } - - getCollection(type: string, selector?: any): Array { - // @ts-ignore - return this.db.get(type).filter(selector).value() - } - - populateItem(type: string, data: DbType, selector?: any, id?: string): DbType | undefined { - // @ts-ignore - if (selector && this.db.get(type).find(selector).value()) return undefined - return ( - this.db - .get(type) - // @ts-ignore - .push(data) - .last() - .assign({ id: id || uuidv4() }) - .write() - ) - } - - createItem(type: string, data: Record, selector?: Record, id?: string): DbType { - // @ts-ignore - if (selector && this.db.get(type).find(selector).value()) - throw new AlreadyExists(`Item already exists in '${type}' collection: ${JSON.stringify(selector)}`) - const cleanData = { ...data, ...selector } - const ret = this.populateItem(type, cleanData, selector, id) - return ret - } - - deleteItem(type: string, selector: any): void { - this.getItemReference(type, selector) - // @ts-ignore - this.db.get(type).remove(selector).write() - } - - updateItem(type: string, data: Record, selector: Record, merge = false): DbType { - const prev = this.getItemReference(type, selector) - const col = this.db.get(type) - // @ts-ignore - const idx = col.findIndex(selector).value() - const merged = (merge && prev ? mergeData(prev, data) : data) as Record - const newData = { ...merged, ...selector } - col.value().splice(idx, 1, newData) - return newData - } -} diff --git a/src/middleware/jwt.test.ts b/src/middleware/jwt.test.ts index 27de72ba1..ad5a76238 100644 --- a/src/middleware/jwt.test.ts +++ b/src/middleware/jwt.test.ts @@ -3,6 +3,8 @@ import OtomiStack from 'src/otomi-stack' import { getUser } from './jwt' import * as getValuesSchemaModule from '../utils' import { loadSpec } from '../app' +import { mockDeep } from 'jest-mock-extended' +import { Git } from '../git' const email = 'test@user.net' const platformAdminGroups = ['platform-admin', 'all-teams-admin'] @@ -31,7 +33,9 @@ describe('JWT claims mapping', () => { beforeEach(async () => { otomiStack = new OtomiStack() + otomiStack.git = mockDeep() await otomiStack.init() + await otomiStack.loadValues() }) test('A user in either platform-admin or all-teams-admin group should get platformAdmin role and have isPlatformAdmin', () => { diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index 75e567aab..b228af811 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -1,10 +1,12 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { mockDeep } from 'jest-mock-extended' -import { App, Coderepo, User, Workload } from 'src/otomi-models' +import { App, Policies, Team, TeamConfig, User, Coderepo, Workload } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' -import { Repo } from 'src/repo' -import { loadSpec } from './app' +import { mockDeep } from 'jest-mock-extended' import { PublicUrlExists } from './error' +import { loadSpec } from './app' +import { Git } from './git' +import { RepoService } from './services/RepoService' +import { TeamConfigService } from './services/TeamConfigService' jest.mock('src/utils', () => { const originalModule = jest.requireActual('src/utils') @@ -17,6 +19,16 @@ jest.mock('src/utils', () => { } }) +jest.mock('src/utils/userUtils', () => { + const originalModule = jest.requireActual('src/utils/userUtils') + + return { + __esModule: true, + ...originalModule, + getKeycloakUsers: jest.fn().mockResolvedValue([]), + } +}) + beforeAll(async () => { jest.spyOn(console, 'log').mockImplementation(() => {}) jest.spyOn(console, 'debug').mockImplementation(() => {}) @@ -28,61 +40,95 @@ beforeAll(async () => { describe('Data validation', () => { let otomiStack: OtomiStack + const teamId = 'aa' + let mockRepoService: jest.Mocked + let mockTeamConfigService: jest.Mocked beforeEach(async () => { otomiStack = new OtomiStack() await otomiStack.init() - otomiStack.repo = mockDeep() + otomiStack.git = mockDeep() + mockRepoService = mockDeep() + otomiStack.repoService = mockRepoService + + // Mock TeamConfigService + mockTeamConfigService = mockDeep() + + // Mocking getServices() to return a list of services + mockTeamConfigService.getServices.mockReturnValue([ + { + name: 'svc', + ingress: { domain: 'a.com', subdomain: 'b' }, + }, + { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b', paths: ['/test/'] } }, + ]) + + // Ensure getTeamConfigService() returns our mocked TeamConfigService + mockRepoService.getTeamConfigService.mockReturnValue(mockTeamConfigService) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() }) - test('should throw exception on duplicated domain', async () => { + test('should throw exception on duplicated domain', () => { const svc = { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b' } } - const svc1 = { ...svc } - await otomiStack.createService('aa', svc) - expect(() => otomiStack.checkPublicUrlInUse(svc1)).toThrow(new PublicUrlExists()) + expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).toThrow(new PublicUrlExists()) }) - test('should throw exception on duplicated url with path', async () => { + test('should throw exception on duplicated url with path', () => { const svc = { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b', paths: ['/test/'] } } - await otomiStack.createService('aa', svc) - expect(() => otomiStack.checkPublicUrlInUse(svc)).toThrow(new PublicUrlExists()) + expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).toThrow(new PublicUrlExists()) }) - test('should not throw exception on unique url', async () => { - const svc1 = { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b', paths: ['/test/'] } } - await otomiStack.createService('aa', svc1) - const svc2 = { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b' } } + test('should not throw exception on unique url', () => { const svc3 = { name: 'svc', ingress: { domain: 'a.com', subdomain: 'b', paths: ['/bla'] } } - expect(() => otomiStack.checkPublicUrlInUse(svc2)).not.toThrow() - expect(() => otomiStack.checkPublicUrlInUse(svc3)).not.toThrow() + expect(() => otomiStack.checkPublicUrlInUse(teamId, svc3)).not.toThrow() }) - test('should not throw exception when of type cluster', async () => { + test('should not throw exception when of type cluster', () => { const svc = { name: 'svc', ingress: { type: 'cluster' } } - // @ts-ignore - await otomiStack.createService('aa', svc) - expect(() => otomiStack.checkPublicUrlInUse(svc)).not.toThrow() + expect(() => otomiStack.checkPublicUrlInUse(teamId, svc)).not.toThrow() }) - test('should not throw exception when editing', async () => { + test('should not throw exception when editing', () => { const svc = { id: 'x1', name: 'svc', ingress: { domain: 'a.com', subdomain: 'b', paths: ['/test/'] } } - await otomiStack.createService('aa', svc) const svc1 = { id: 'x1', name: 'svc', ingress: { domain: 'a.com', subdomain: 'c' } } - expect(() => otomiStack.checkPublicUrlInUse(svc1)).not.toThrow() + expect(() => otomiStack.checkPublicUrlInUse(teamId, svc1)).not.toThrow() }) test('should create a password when password is not specified', async () => { - const createItemSpy = jest.spyOn(otomiStack.db, 'createItem') - await otomiStack.createTeam({ name: 'test' }) + const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ + builds: [], + workloads: [], + services: [], + sealedSecrets: [], + backups: [], + projects: [], + netpols: [], + settings: {} as Team, + apps: [], + policies: {} as Policies, + workloadValues: [], + } as TeamConfig) + await otomiStack.createTeam({ name: 'test' }, false) expect(createItemSpy.mock.calls[0][1].password).not.toEqual('') createItemSpy.mockRestore() }) test('should not create a password when password is specified', async () => { - const createItemSpy = jest.spyOn(otomiStack.db, 'createItem') + const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ + builds: [], + workloads: [], + services: [], + sealedSecrets: [], + backups: [], + projects: [], + netpols: [], + settings: {} as Team, + apps: [], + policies: {} as Policies, + workloadValues: [], + } as TeamConfig) const myPassword = 'someAwesomePassword' - await otomiStack.createTeam({ name: 'test', password: myPassword }) + await otomiStack.createTeam({ name: 'test', password: myPassword }, false) expect(createItemSpy.mock.calls[0][1].password).toEqual(myPassword) createItemSpy.mockRestore() }) @@ -93,9 +139,8 @@ describe('Work with values', () => { beforeEach(async () => { otomiStack = new OtomiStack() await otomiStack.init() - otomiStack.repo = new Repo('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) + otomiStack.git = new Git('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - jest.spyOn(otomiStack, 'loadApp').mockResolvedValue() }) test('can load from configuration to database and back', async () => { @@ -108,37 +153,10 @@ describe('Workload values', () => { beforeEach(async () => { otomiStack = new OtomiStack() await otomiStack.init() - otomiStack.repo = new Repo('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) + otomiStack.git = new Git('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() }) - test('can load workload values (empty dict)', async () => { - const w: Workload = { id: '1', teamId: '2', name: 'name', url: 'https://test.local' } - - otomiStack.repo.fileExists = jest.fn().mockReturnValue(true) - otomiStack.repo.readFile = jest.fn().mockReturnValue({}) - const res = await otomiStack.loadWorkloadValues(w) - expect(res).toEqual({ id: '1', teamId: '2', name: 'name', values: {} }) - }) - - test('can load workload values (dict)', async () => { - const w: Workload = { id: '1', teamId: '2', name: 'name', url: 'https://test.local' } - - otomiStack.repo.fileExists = jest.fn().mockReturnValue(true) - otomiStack.repo.readFile = jest.fn().mockReturnValue({ values: 'test: 1' }) - const res = await otomiStack.loadWorkloadValues(w) - expect(res).toEqual({ id: '1', teamId: '2', name: 'name', values: { test: 1 } }) - }) - - test('can load workload values (empty string)', async () => { - const w: Workload = { id: '1', teamId: '2', name: 'name', url: 'https://test.local' } - - otomiStack.repo.fileExists = jest.fn().mockReturnValue(true) - otomiStack.repo.readFile = jest.fn().mockReturnValue({ values: '' }) - const res = await otomiStack.loadWorkloadValues(w) - expect(res).toEqual({ id: '1', teamId: '2', name: 'name', values: {} }) - }) - test('returns filtered apps if App array is submitted isPreinstalled flag is true', () => { const apps: App[] = [{ id: 'external-dns' }, { id: 'drone' }, { id: 'cnpg' }, { id: 'loki' }] jest.spyOn(otomiStack, 'getSettingsInfo').mockReturnValue({ otomi: { isPreInstalled: true } }) @@ -181,21 +199,16 @@ describe('Users tests', () => { beforeEach(async () => { otomiStack = new OtomiStack() await otomiStack.init() - otomiStack.repo = mockDeep() - otomiStack.loadApps = jest.fn().mockResolvedValue([]) - const dbMock = mockDeep() - dbMock.getItem.mockImplementation((collection, query) => { - return [defaultPlatformAdmin, anyPlatformAdmin].find((user) => user.id === query.id) - }) - dbMock.deleteItem.mockReturnValue() - - otomiStack.db = dbMock + otomiStack.git = mockDeep() jest.spyOn(otomiStack, 'getSettings').mockReturnValue({ cluster: { domainSuffix }, }) - jest.spyOn(otomiStack, 'saveUsers').mockResolvedValue() + jest.spyOn(otomiStack, 'saveUser').mockResolvedValue() jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + await otomiStack.initRepo() + await otomiStack.createUser(defaultPlatformAdmin) + await otomiStack.createUser(anyPlatformAdmin) }) afterEach(() => { From b0e852dc63209031cc484f89e199f2e95a644e08 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 24 Feb 2025 11:47:41 +0100 Subject: [PATCH 12/49] fix: unit tests --- jest.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.ts b/jest.config.ts index 6b8a60503..0766224de 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -13,5 +13,5 @@ const config: Config.InitialOptions = { } export default config process.env = Object.assign(process.env, { - NODE_ENV: 'test' + NODE_ENV: 'test', }) From 4712b790b6529af296490b5e38d9542974a3460c Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 24 Feb 2025 14:11:10 +0100 Subject: [PATCH 13/49] fix: unit tests --- src/api.authz.test.ts | 7 +++---- src/middleware/authz.ts | 21 ++++++++++++--------- src/otomi-stack.test.ts | 1 + src/services/RepoService.ts | 2 +- 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index 05e13f358..7a5cd5bf7 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -43,14 +43,13 @@ describe('API authz tests', () => { _otomiStack.git = mockDeep() _otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) await _otomiStack.initRepo() - await _otomiStack.createTeam({ name: 'team1' }) - await _otomiStack.createTeam({ name: 'team2' }) otomiStack = _otomiStack as jest.Mocked - otomiStack.createTeam = jest.fn().mockResolvedValue(undefined) otomiStack.saveTeam = jest.fn().mockResolvedValue(undefined) otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) - await otomiStack.init() + await otomiStack.loadValues() + await otomiStack.createTeam({ name: 'team1' }) + await otomiStack.createTeam({ name: 'team2' }) app = await initApp(otomiStack) agent = request.agent(app) agent.set('Accept', 'application/json') diff --git a/src/middleware/authz.ts b/src/middleware/authz.ts index 2629a37f1..147c8683f 100644 --- a/src/middleware/authz.ts +++ b/src/middleware/authz.ts @@ -104,17 +104,20 @@ export function authorize(req: OpenApiRequestExt, res, next, authz: Authz, repoS {}, ) - try { - let collection - if (teamSpecificCollections.includes(collectionId)) { - collection = repoService.getTeamConfigService(teamId).getCollection(collectionId) - } else { - collection = repoService.getCollection(collectionId) + if (action === 'update') { + try { + let collection + if (teamSpecificCollections.includes(collectionId)) { + collection = repoService.getTeamConfigService(teamId).getCollection(collectionId) + } else { + collection = repoService.getCollection(collectionId) + } + dataOrig = find(collection, selector) || {} + } catch (error) { + debug('Error in authzMiddleware', error) } - dataOrig = find(collection, selector) || {} - } catch (error) { - debug('Error in authzMiddleware', error) } + const violatedAttributes = authz.validateWithAbac(action, schemaName, teamId, req.body, dataOrig) if (violatedAttributes.length > 0) { return res.status(403).send({ diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index b228af811..c0d79e7d1 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -206,6 +206,7 @@ describe('Users tests', () => { }) jest.spyOn(otomiStack, 'saveUser').mockResolvedValue() jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'getApp').mockReturnValue({ id: 'keycloak' }) await otomiStack.initRepo() await otomiStack.createUser(defaultPlatformAdmin) await otomiStack.createUser(anyPlatformAdmin) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 3dafa26fe..e26b66870 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -67,7 +67,7 @@ export class RepoService { public getApp(id: string): App { const app = find(this.repo.apps, { id }) if (!app) { - throw new Error(`User[${id}] does not exist.`) + throw new Error(`App[${id}] does not exist.`) } return app } From 2b1271936f5a31a3e47289be7787f086d6833647 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 24 Feb 2025 16:54:33 +0100 Subject: [PATCH 14/49] feat: add tests for the repo and teamconfig service --- src/openapi/settings.yaml | 19 ++ src/otomi-models.ts | 3 +- src/otomi-stack.ts | 1 + src/services/RepoSerivce.test.ts | 188 +++++++++++++ src/services/RepoService.ts | 3 +- src/services/TeamConfigService.test.ts | 376 +++++++++++++++++++++++++ src/services/TeamConfigService.ts | 4 +- 7 files changed, 590 insertions(+), 4 deletions(-) create mode 100644 src/services/RepoSerivce.test.ts create mode 100644 src/services/TeamConfigService.test.ts diff --git a/src/openapi/settings.yaml b/src/openapi/settings.yaml index 2efbdf738..5f60aa681 100644 --- a/src/openapi/settings.yaml +++ b/src/openapi/settings.yaml @@ -326,6 +326,25 @@ Settings: nodeSelector: $ref: definitions.yaml#/labels description: 'One or more label/value pairs of one or more nodes. This will enforce scheduling of all platform services on these nodes.' + #TODO move this one out of otomi and in to versions + version: + title: Version + default: latest + description: | + Set the version to a valid release found in the linode/apl-core Github repository. + type: string + required: + - version + x-externalDocsPath: docs/for-ops/console/settings/platform + x-acl: + platformAdmin: [read-any, update-any] + teamAdmin: [read] + teamMember: [read] + versions: + title: Platform + type: object + additionalProperties: false + properties: version: title: Version default: latest diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 9b15645cd..2122514dc 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -43,6 +43,7 @@ export type Smtp = Settings['smtp'] export type Kms = Settings['kms'] export type Oidc = Settings['oidc'] export type Otomi = Settings['otomi'] +export type Versions = Settings['versions'] export interface OpenApiRequest extends Request { operationDoc: { @@ -163,7 +164,7 @@ export interface Repo { platformBackups: Record smtp: Smtp users: User[] - versions: Record + versions: Versions teamConfig: Record } diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 07b41ba9a..8d2041edd 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -399,6 +399,7 @@ export default class OtomiStack { 'otomi', 'platformBackups', 'smtp', + 'versions', ]) return settings } diff --git a/src/services/RepoSerivce.test.ts b/src/services/RepoSerivce.test.ts new file mode 100644 index 000000000..66e6f24b1 --- /dev/null +++ b/src/services/RepoSerivce.test.ts @@ -0,0 +1,188 @@ +import { App, Repo, User } from '../otomi-models' +import { RepoService } from './RepoService' +import { TeamConfigService } from './TeamConfigService' +import { AlreadyExists } from '../error' + +jest.mock('uuid', () => ({ + v4: jest.fn(() => 'mocked-uuid'), +})) + +describe('RepoService', () => { + let service: RepoService + let repo: Repo + + beforeEach(() => { + repo = { + apps: [], + users: [], + teamConfig: {}, + cluster: {}, + dns: {}, + ingress: {}, + otomi: { version: '1.0.0' }, + smtp: { smarthost: 'smtp.mailtrap.io' }, + platformBackups: {}, + alerts: {}, + databases: {}, + kms: {}, + obj: {}, + oidc: { issuer: 'https://issuer.com', clientID: 'client-id', clientSecret: 'client-secret' }, + versions: { version: '1.0.0' }, + } as Repo + service = new RepoService(repo) + }) + + describe('getTeamConfigService', () => { + test('should throw an error if team config does not exist', () => { + expect(() => service.getTeamConfigService('nonexistent-team')).toThrow( + 'TeamConfig for nonexistent-team does not exist.', + ) + }) + + test('should return an instance of TeamConfigService when team config exists', () => { + service.createTeamConfig('team1', { name: 'Team 1' }) + const teamConfigService = service.getTeamConfigService('team1') + + expect(teamConfigService).toBeInstanceOf(TeamConfigService) + expect(service.getTeamConfig('team1')).toBeDefined() + }) + }) + + describe('Users', () => { + const user: User = { email: 'user@test.com', firstName: 'user', lastName: 'test' } + + test('should create a user', () => { + const createdUser = service.createUser(user) + expect(createdUser).toEqual({ email: 'user@test.com', id: 'mocked-uuid', firstName: 'user', lastName: 'test' }) + expect(service.getUsers()).toHaveLength(1) + }) + + test('should throw an error if user already exists', () => { + service.createUser(user) + expect(() => service.createUser(user)).toThrow(AlreadyExists) + }) + + test('should retrieve a user by ID', () => { + const createdUser = service.createUser(user) + expect(service.getUser(createdUser.id!)).toEqual(createdUser) + }) + + test('should delete a user', () => { + const createdUser = service.createUser(user) + service.deleteUser(createdUser.id!) + expect(service.getUsers()).toHaveLength(0) + }) + }) + + describe('Apps', () => { + const app: App = { id: 'app1', enabled: true } + + test('should retrieve all apps', () => { + service.getRepo().apps.push(app) + expect(service.getApps()).toContain(app) + }) + + test('should retrieve a specific app', () => { + service.getRepo().apps.push(app) + expect(service.getApp('app1')).toEqual(app) + }) + + test('should throw an error when retrieving a non-existent app', () => { + expect(() => service.getApp('nonexistent')).toThrow('App[nonexistent] does not exist.') + }) + + test('should update an app', () => { + service.getRepo().apps.push(app) + const updatedApp = service.updateApp('app1', { enabled: false }) + expect(updatedApp.enabled).toBe(false) + }) + + test('should delete an app', () => { + service.getRepo().apps.push(app) + service.deleteApp('app1') + expect(service.getApps()).toHaveLength(0) + }) + }) + + describe('Team Config', () => { + test('should create a team config', () => { + const teamConfig = service.createTeamConfig('team1', { name: 'Team 1' }) + expect(teamConfig.settings).toEqual({ name: 'Team 1', id: 'team1' }) + expect(service.getTeamConfig('team1')).toBeDefined() + }) + + test('should throw an error if team config already exists', () => { + service.createTeamConfig('team1', { name: 'Team 1' }) + expect(() => service.createTeamConfig('team1', { name: 'Duplicate Team' })).toThrow(AlreadyExists) + }) + + test('should delete a team config', () => { + service.createTeamConfig('team1', { name: 'Team 1' }) + service.deleteTeamConfig('team1') + expect(service.getTeamConfig('team1')).toBeUndefined() + }) + }) + + describe('Collection Functions', () => { + test('should retrieve a collection', () => { + service.getRepo().cluster = { name: 'Test Cluster' } + expect(service.getCollection('cluster')).toEqual({ name: 'Test Cluster' }) + }) + + test('should throw an error for non-existent collection', () => { + expect(() => service.getCollection('nonexistent')).toThrow( + 'Getting repo collection [nonexistent] does not exist.', + ) + }) + + test('should update an existing collection', () => { + service.getRepo().cluster = { name: 'Old Cluster' } + service.updateCollection('cluster', { name: 'Updated Cluster' }) + expect(service.getCollection('cluster')).toEqual({ name: 'Updated Cluster' }) + }) + + test('should throw an error when updating a non-existent collection', () => { + expect(() => service.updateCollection('nonexistent', { key: 'value' })).toThrow( + 'Updating repo collection [nonexistent] does not exist.', + ) + }) + }) + + describe('Settings', () => { + test('should retrieve settings', () => { + service.getRepo().cluster = { name: 'Cluster A' } + service.getRepo().dns = { provider: { linode: { apiToken: 'test' } } } + const settings = service.getSettings() + + expect(settings.cluster).toEqual({ name: 'Cluster A' }) + expect(settings.dns).toEqual({ provider: { linode: { apiToken: 'test' } } }) + }) + + test('should update settings', () => { + service.updateSettings({ cluster: { name: 'Updated Cluster' } }) + expect(service.getSettings().cluster).toEqual({ name: 'Updated Cluster' }) + }) + }) + + describe('Global Retrieval Functions', () => { + test('should return all users emails', () => { + service.createUser({ email: 'user1@test.com', firstName: 'user', lastName: 'test' }) + service.createUser({ email: 'user2@test.com', firstName: 'user', lastName: 'test' }) + expect(service.getUsersEmail()).toEqual(['user1@test.com', 'user2@test.com']) + }) + + test('should return all builds', () => { + service.createTeamConfig('team1', { name: 'Team 1' }) + + service.getTeamConfigService('team1').createBuild({ name: 'Build1' }) + expect(service.getAllBuilds()).toHaveLength(1) + }) + + test('should return all projects', () => { + service.createTeamConfig('team1', { name: 'Team 1' }) + + service.getTeamConfigService('team1').createProject({ name: 'Project1' }) + expect(service.getAllProjects()).toHaveLength(1) + }) + }) +}) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index e26b66870..401314bb4 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -20,6 +20,7 @@ import { Team, TeamConfig, User, + Versions, Workload, } from '../otomi-models' import { TeamConfigService } from './TeamConfigService' @@ -45,7 +46,7 @@ export class RepoService { this.repo.platformBackups ??= {} this.repo.smtp ??= {} as Smtp this.repo.users ??= [] - this.repo.versions ??= {} + this.repo.versions ??= {} as Versions this.repo.teamConfig ??= {} } diff --git a/src/services/TeamConfigService.test.ts b/src/services/TeamConfigService.test.ts new file mode 100644 index 000000000..5f45cbb23 --- /dev/null +++ b/src/services/TeamConfigService.test.ts @@ -0,0 +1,376 @@ +// Mock UUID to generate predictable values +import { + App, + Backup, + Build, + Netpol, + Project, + SealedSecret, + Service, + TeamConfig, + Workload, + WorkloadValues, +} from '../otomi-models' +import { TeamConfigService } from './TeamConfigService' +import { AlreadyExists, NotExistError } from '../error' + +jest.mock('uuid', () => ({ + v4: jest.fn(() => 'mocked-uuid'), +})) + +describe('TeamConfigService', () => { + let service: TeamConfigService + let teamConfig: TeamConfig + + beforeEach(() => { + teamConfig = { + builds: [], + workloads: [], + workloadValues: [], + services: [], + sealedSecrets: [], + backups: [], + projects: [], + netpols: [], + apps: [], + policies: {}, + settings: { name: 'team1' }, + } as TeamConfig + service = new TeamConfigService(teamConfig) + }) + + describe('Builds', () => { + const build: Build = { name: 'TestBuild' } + test('should create a build', () => { + const createdBuild = service.createBuild(build) + + expect(createdBuild).toEqual({ name: 'TestBuild', id: 'mocked-uuid' }) + expect(service.getBuilds()).toHaveLength(1) + }) + + test('should throw an error if creating a duplicate build', () => { + service.createBuild(build) + + expect(() => service.createBuild(build)).toThrow(AlreadyExists) + }) + + test('should retrieve a build by id', () => { + const createdBuild = service.createBuild(build) + + expect(service.getBuild(createdBuild.id!)).toEqual(createdBuild) + }) + + test('should throw an error when retrieving a non-existent build', () => { + expect(() => service.getBuild('non-existent')).toThrow(NotExistError) + }) + + test('should update a build', () => { + const createdBuild = service.createBuild(build) + + const updatedBuild = service.updateBuild(createdBuild.id!, { name: 'UpdatedBuild' }) + expect(updatedBuild.name).toBe('UpdatedBuild') + }) + + test('should delete a build', () => { + const createdBuild = service.createBuild(build) + + service.deleteBuild(createdBuild.id!) + expect(service.getBuilds()).toHaveLength(0) + }) + }) + + describe('Workloads', () => { + const workload: Workload = { name: 'TestWorkload', url: 'http://test.com' } + test('should create a workload', () => { + const createdWorkload = service.createWorkload(workload) + + expect(createdWorkload).toEqual({ name: 'TestWorkload', id: 'mocked-uuid', url: 'http://test.com' }) + expect(service.getWorkloads()).toHaveLength(1) + }) + + test('should throw an error if creating a duplicate workload', () => { + service.createWorkload(workload) + + expect(() => service.createWorkload(workload)).toThrow(AlreadyExists) + }) + + test('should retrieve a workload by id', () => { + const createdWorkload = service.createWorkload(workload) + + expect(service.getWorkload(createdWorkload.id!)).toEqual(createdWorkload) + }) + + test('should throw an error when retrieving a non-existent workload', () => { + expect(() => service.getWorkload('non-existent')).toThrow(NotExistError) + }) + + test('should delete a workload', () => { + const createdWorkload = service.createWorkload(workload) + + service.deleteWorkload(createdWorkload.id!) + expect(service.getWorkloads()).toHaveLength(0) + }) + }) + + describe('Services', () => { + const serviceData: Service = { name: 'TestService', ingress: {} } + test('should create a service', () => { + const createdService = service.createService(serviceData) + + expect(createdService).toEqual({ name: 'TestService', id: 'mocked-uuid', ingress: {} }) + expect(service.getServices()).toHaveLength(1) + }) + + test('should throw an error if creating a duplicate service', () => { + service.createService(serviceData) + + expect(() => service.createService(serviceData)).toThrow(AlreadyExists) + }) + + test('should retrieve a service by id', () => { + const createdService = service.createService(serviceData) + + expect(service.getService(createdService.id!)).toEqual(createdService) + }) + + test('should throw an error when retrieving a non-existent service', () => { + expect(() => service.getService('non-existent')).toThrow(NotExistError) + }) + + test('should delete a service', () => { + const createdService = service.createService(serviceData) + + service.deleteService(createdService.name) + expect(service.getServices()).toHaveLength(0) + }) + }) + + describe('SealedSecrets', () => { + const secret: SealedSecret = { + name: 'TestSecret', + type: 'kubernetes.io/opaque', + encryptedData: [{ key: 'key', value: 'value' }], + } + test('should create a sealed secret', () => { + const createdSecret = service.createSealedSecret(secret) + + expect(createdSecret).toEqual({ + name: 'TestSecret', + id: 'mocked-uuid', + type: 'kubernetes.io/opaque', + encryptedData: [{ key: 'key', value: 'value' }], + }) + expect(service.getSealedSecrets()).toHaveLength(1) + }) + + test('should throw an error if creating a duplicate sealed secret', () => { + service.createSealedSecret(secret) + + expect(() => service.createSealedSecret(secret)).toThrow(AlreadyExists) + }) + + test('should retrieve a sealed secret by id', () => { + const createdSecret = service.createSealedSecret(secret) + + expect(service.getSealedSecret(createdSecret.id!)).toEqual(createdSecret) + }) + + test('should throw an error when retrieving a non-existent sealed secret', () => { + expect(() => service.getSealedSecret('non-existent')).toThrow(NotExistError) + }) + + test('should delete a sealed secret', () => { + const createdSecret = service.createSealedSecret(secret) + + service.deleteSealedSecret(createdSecret.id!) + expect(service.getSealedSecrets()).toHaveLength(0) + }) + }) + + describe('WorkloadValues', () => { + const workloadValues: WorkloadValues = { name: 'TestWorkloadValues', values: { test: 'values' } } + + test('should create workload values', () => { + const created = service.createWorkloadValues(workloadValues) + expect(created).toEqual({ name: 'TestWorkloadValues', id: 'mocked-uuid', values: { test: 'values' } }) + expect(service.getWorkloadValues(created.id!)).toEqual(created) + }) + + test('should throw an error when creating duplicate workload values', () => { + service.createWorkloadValues(workloadValues) + expect(() => service.createWorkloadValues(workloadValues)).toThrow(AlreadyExists) + }) + + test('should delete workload values', () => { + const created = service.createWorkloadValues(workloadValues) + service.deleteWorkloadValues(created.id!) + expect(() => service.getWorkloadValues(created.id!)).toThrow(NotExistError) + }) + }) + + describe('Backups', () => { + const backup: Backup = { name: 'TestBackup', ttl: '1', schedule: '0 0 * * *' } + + test('should create a backup', () => { + const created = service.createBackup(backup) + expect(created).toEqual({ name: 'TestBackup', id: 'mocked-uuid', ttl: '1', schedule: '0 0 * * *' }) + expect(service.getBackup(created.id!)).toEqual(created) + }) + + test('should throw an error when creating duplicate backup', () => { + service.createBackup(backup) + expect(() => service.createBackup(backup)).toThrow(AlreadyExists) + }) + + test('should delete a backup', () => { + const created = service.createBackup(backup) + service.deleteBackup(created.id!) + expect(() => service.getBackup(created.id!)).toThrow(NotExistError) + }) + }) + + describe('Projects', () => { + const project: Project = { name: 'TestProject' } + + test('should create a project', () => { + const created = service.createProject(project) + expect(created).toEqual({ name: 'TestProject', id: 'mocked-uuid' }) + expect(service.getProject(created.id!)).toEqual(created) + }) + + test('should throw an error when creating duplicate project', () => { + service.createProject(project) + expect(() => service.createProject(project)).toThrow(AlreadyExists) + }) + + test('should delete a project', () => { + const created = service.createProject(project) + service.deleteProject(created.id!) + expect(() => service.getProject(created.id!)).toThrow(NotExistError) + }) + }) + + describe('Netpols', () => { + const netpol: Netpol = { name: 'TestNetpol' } + + test('should create a netpol', () => { + const created = service.createNetpol(netpol) + expect(created).toEqual({ name: 'TestNetpol', id: 'mocked-uuid' }) + expect(service.getNetpol(created.id!)).toEqual(created) + }) + + test('should throw an error when creating duplicate netpol', () => { + service.createNetpol(netpol) + expect(() => service.createNetpol(netpol)).toThrow(AlreadyExists) + }) + + test('should delete a netpol', () => { + const created = service.createNetpol(netpol) + service.deleteNetpol(created.id!) + expect(() => service.getNetpol(created.id!)).toThrow(NotExistError) + }) + }) + + describe('Apps', () => { + const app: App = { id: 'app1' } + + test('should create an app', () => { + const created = service.createApp(app) + expect(created).toEqual({ id: 'app1' }) + expect(service.getApp(created.id)).toEqual(created) + }) + + test('should throw an error when creating duplicate app', () => { + service.createApp(app) + expect(() => service.createApp(app)).toThrow(AlreadyExists) + }) + }) + + describe('Policies', () => { + test('should retrieve policies', () => { + expect(service.getPolicies()).toEqual({}) + }) + + test('should update policies', () => { + service.updatePolicies({ 'require-limits': { action: 'Audit' } }) + expect(service.getPolicies()).toEqual({ 'require-limits': { action: 'Audit' } }) + }) + + test('should retrieve a single policy', () => { + service.updatePolicies({ 'require-limits': { action: 'Audit' } }) + expect(service.getPolicy('require-limits')).toEqual({ action: 'Audit' }) + }) + }) + + describe('Settings', () => { + test('should retrieve settings', () => { + expect(service.getSettings()).toEqual({ name: 'team1' }) + }) + + test('should update settings', () => { + const updated = service.updateSettings({ name: 'UpdatedTeam' }) + expect(updated).toEqual({ name: 'UpdatedTeam' }) + expect(service.getSettings().name).toBe('UpdatedTeam') + }) + }) + + describe('doesProjectNameExist', () => { + test('should return false when no projects exist', () => { + expect(service.doesProjectNameExist('NonExistentProject')).toBe(false) + }) + + test('should return true when a build with the given name exists', () => { + service.createBuild({ name: 'ExistingBuild' }) + expect(service.doesProjectNameExist('ExistingBuild')).toBe(true) + }) + + test('should return true when a workload with the given name exists', () => { + service.createWorkload({ name: 'ExistingWorkload', url: 'http://example.com' }) + expect(service.doesProjectNameExist('ExistingWorkload')).toBe(true) + }) + + test('should return true when a service with the given name exists', () => { + service.createService({ name: 'ExistingService', ingress: {} }) + expect(service.doesProjectNameExist('ExistingService')).toBe(true) + }) + + test('should return false when the name does not match any existing project', () => { + service.createBuild({ name: 'SomeBuild' }) + service.createWorkload({ name: 'SomeWorkload', url: 'http://example.com' }) + service.createService({ name: 'SomeService', ingress: {} }) + expect(service.doesProjectNameExist('NonExistentProject')).toBe(false) + }) + }) + + describe('getCollection', () => { + test('should retrieve an existing collection', () => { + service.createBuild({ name: 'TestBuild' }) + expect(service.getCollection('builds')).toEqual([{ name: 'TestBuild', id: expect.any(String) }]) + }) + + test('should throw an error when trying to retrieve a non-existent collection', () => { + expect(() => service.getCollection('nonExistentCollection')).toThrowError( + 'Getting TeamConfig collection [nonExistentCollection] does not exist.', + ) + }) + }) + + describe('updateCollection', () => { + test('should update an existing collection', () => { + service.createBuild({ name: 'Build1' }) + service.updateCollection('builds', [{ name: 'UpdatedBuild' }]) + expect(service.getCollection('builds')).toEqual([{ name: 'UpdatedBuild' }]) + }) + + test('should create a new collection if it does not exist', () => { + service.updateCollection('customCollection', [{ key: 'value' }]) + expect(service.getCollection('customCollection')).toEqual([{ key: 'value' }]) + }) + + test('should replace the collection with the new value', () => { + service.createBuild({ name: 'OldBuild' }) + service.updateCollection('builds', [{ name: 'NewBuild' }]) + expect(service.getCollection('builds')).toEqual([{ name: 'NewBuild' }]) + }) + }) +}) diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index d6042348b..20e5268b6 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -316,8 +316,8 @@ export class TeamConfigService { return merge(netpol, updates) } - public deleteNetpol(name: string): void { - remove(this.teamConfig.netpols, { name }) + public deleteNetpol(id: string): void { + remove(this.teamConfig.netpols, { id }) } // ===================================== From b197d77ae6c14fec2e6ddb46068323c5a59fdd42 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 09:31:06 +0100 Subject: [PATCH 15/49] feat: merge coderepos --- src/api.authz.test.ts | 1 - .../teams/{teamId}/coderepos/{coderepoId}.ts | 11 +- src/otomi-models.ts | 1 + src/otomi-stack.test.ts | 826 +++++++++--------- src/otomi-stack.ts | 64 +- src/repo.ts | 10 + src/services/RepoService.ts | 6 + src/services/TeamConfigService.test.ts | 1 + src/services/TeamConfigService.ts | 37 + 9 files changed, 508 insertions(+), 449 deletions(-) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index 7a5cd5bf7..893e16910 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -9,7 +9,6 @@ import request, { SuperAgentTest } from 'supertest' import { HttpError } from './error' import { getSessionStack } from './middleware' import { App, Coderepo, SealedSecret } from './otomi-models' -import { mockDeep } from 'jest-mock-extended' import * as getValuesSchemaModule from './utils' import { Git } from './git' diff --git a/src/api/teams/{teamId}/coderepos/{coderepoId}.ts b/src/api/teams/{teamId}/coderepos/{coderepoId}.ts index 78590afb9..489609519 100644 --- a/src/api/teams/{teamId}/coderepos/{coderepoId}.ts +++ b/src/api/teams/{teamId}/coderepos/{coderepoId}.ts @@ -6,25 +6,24 @@ const debug = Debug('otomi:api:teams:coderepos') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { coderepoId } }: OpenApiRequestExt, res): Promise => { + async ({ otomi, params: { teamId, coderepoId } }: OpenApiRequestExt, res): Promise => { debug(`deleteCoderepo(${coderepoId})`) - await otomi.deleteCoderepo(decodeURIComponent(coderepoId)) + await otomi.deleteCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { coderepoId } }: OpenApiRequestExt, res): void => { + ({ otomi, params: { teamId, coderepoId } }: OpenApiRequestExt, res): void => { debug(`getCoderepo(${coderepoId})`) - const data = otomi.getCoderepo(decodeURIComponent(coderepoId)) + const data = otomi.getCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId)) res.json(data) }, ] const put: Operation = [ async ({ otomi, params: { teamId, coderepoId }, body }: OpenApiRequestExt, res): Promise => { debug(`editCoderepo(${coderepoId})`) - const data = await otomi.editCoderepo(decodeURIComponent(coderepoId), { + const data = await otomi.editCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId), { ...body, - teamId: decodeURIComponent(teamId), } as Coderepo) res.json(data) }, diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 2122514dc..e7134b84d 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -172,6 +172,7 @@ export interface TeamConfig { apps: App[] backups: Backup[] builds: Build[] + coderepos: Coderepo[] netpols: Netpol[] policies: Policies projects: Project[] diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index c0d79e7d1..aa5ba1d3d 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { App, Policies, Team, TeamConfig, User, Coderepo, Workload } from 'src/otomi-models' +import { App, Policies, Team, TeamConfig, User } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' import { mockDeep } from 'jest-mock-extended' import { PublicUrlExists } from './error' @@ -97,6 +97,7 @@ describe('Data validation', () => { test('should create a password when password is not specified', async () => { const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ builds: [], + coderepos: [], workloads: [], services: [], sealedSecrets: [], @@ -116,6 +117,7 @@ describe('Data validation', () => { test('should not create a password when password is specified', async () => { const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ builds: [], + coderepos: [], workloads: [], services: [], sealedSecrets: [], @@ -289,413 +291,415 @@ describe('Users tests', () => { }) }) -describe('Code repositories tests', () => { - let otomiStack: OtomiStack - - beforeEach(async () => { - otomiStack = new OtomiStack() - await otomiStack.init() - const dbMock = mockDeep() - dbMock.deleteItem.mockReturnValue() - otomiStack.db = dbMock - }) - - afterEach(() => { - jest.restoreAllMocks() - }) - - test('should create an internal code repository', async () => { - const createItemSpy = jest.spyOn(otomiStack.db, 'createItem').mockReturnValue({ - teamId: 'demo', - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.createCoderepo('demo', { - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }) - - expect(coderepo).toEqual({ - teamId: 'demo', - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }) - expect(createItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }, - { teamId: 'demo', label: 'code-1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - createItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should get an existing internal code repository', () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - } as Coderepo - - jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) - - const result = otomiStack.getCoderepo('1') - expect(result).toEqual(coderepo) - }) - - test('should edit an existing internal code repository', async () => { - const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.editCoderepo('1', { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }) - - expect(coderepo).toEqual({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }) - expect(updateItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - }, - { id: '1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - updateItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should delete an existing internal code repository', async () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'gitea', - repositoryUrl: 'https://gitea.test.com', - } as Coderepo - - jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) - const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - await otomiStack.deleteCoderepo('1') - - expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - deleteItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should create an external public code repository', async () => { - const createItemSpy = jest.spyOn(otomiStack.db, 'createItem').mockReturnValue({ - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.createCoderepo('demo', { - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }) - - expect(coderepo).toEqual({ - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }) - expect(createItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }, - { teamId: 'demo', label: 'code-1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - createItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should get an existing external public code repository', () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - } as Coderepo - - jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) - - const result = otomiStack.getCoderepo('1') - expect(result).toEqual(coderepo) - }) - - test('should edit an existing external public code repository', async () => { - const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.editCoderepo('1', { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }) - - expect(coderepo).toEqual({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }) - expect(updateItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - }, - { id: '1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - updateItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should delete an existing external public code repository', async () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - } as Coderepo - - jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) - const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - await otomiStack.deleteCoderepo('1') - - expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - deleteItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should create an external private code repository', async () => { - const createItemSpy = jest.spyOn(otomiStack.db, 'createItem').mockReturnValue({ - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.createCoderepo('demo', { - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }) - - expect(coderepo).toEqual({ - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }) - expect(createItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }, - { teamId: 'demo', label: 'code-1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - createItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should get an existing external private code repository', () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - } as Coderepo - - jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) - - const result = otomiStack.getCoderepo('1') - expect(result).toEqual(coderepo) - }) - - test('should edit an existing external private code repository', async () => { - const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - } as Coderepo) - - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - const coderepo = await otomiStack.editCoderepo('1', { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }) - - expect(coderepo).toEqual({ - id: '1', - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }) - expect(updateItemSpy).toHaveBeenCalledWith( - 'coderepos', - { - teamId: 'demo', - label: 'code-1-updated', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - }, - { id: '1' }, - ) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - updateItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) - - test('should delete an existing external private code repository', async () => { - const coderepo = { - id: '1', - teamId: 'demo', - label: 'code-1', - gitService: 'github', - repositoryUrl: 'https://github.test.com', - private: true, - secret: 'test', - } as Coderepo - - jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) - const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) - const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepos').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() - - await otomiStack.deleteCoderepo('1') - - expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) - expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') - expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) - - deleteItemSpy.mockRestore() - saveTeamCodereposSpy.mockRestore() - doDeploymentSpy.mockRestore() - }) -}) +// describe('Code repositories tests', () => { +// let otomiStack: OtomiStack +// let teamConfigService: TeamConfigService +// +// beforeEach(async () => { +// otomiStack = new OtomiStack() +// await otomiStack.init() +// await otomiStack.initRepo() +// otomiStack.repoService.createTeamConfig('demo', { name: 'demo' }) +// teamConfigService = otomiStack.repoService.getTeamConfigService('demo') +// jest.spyOn(otomiStack, 'deleteTeamCoderepo').mockResolvedValue() +// }) +// +// afterEach(() => { +// jest.restoreAllMocks() +// }) +// +// test('should create an internal code repository', async () => { +// const createItemSpy = jest.spyOn(teamConfigService, 'createCoderepo').mockReturnValue({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.createCoderepo('demo', { +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }) +// +// expect(coderepo).toEqual({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }) +// expect(createItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }, +// { teamId: 'demo', label: 'code-1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// createItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should get an existing internal code repository', () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// } as Coderepo +// +// jest.spyOn(teamConfigService, 'getCoderepo').mockReturnValue(coderepo) +// +// const result = otomiStack.getCoderepo('demo', '1') +// expect(result).toEqual(coderepo) +// }) +// +// test('should edit an existing internal code repository', async () => { +// const updateItemSpy = jest.spyOn(teamConfigService, 'updateCoderepo').mockReturnValue({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.editCoderepo('demo', '1', { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }) +// +// expect(coderepo).toEqual({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }) +// expect(updateItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// }, +// { id: '1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// updateItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should delete an existing internal code repository', async () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'gitea', +// repositoryUrl: 'https://gitea.test.com', +// } as Coderepo +// +// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) +// const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCoderepo').mockResolvedValue({} as never) +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// await otomiStack.deleteCoderepo('demo', '1') +// +// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// deleteItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should create an external public code repository', async () => { +// const createItemSpy = jest.spyOn(teamConfigService, 'createCoderepo').mockReturnValue({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.createCoderepo('demo', { +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }) +// +// expect(coderepo).toEqual({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }) +// expect(createItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }, +// { teamId: 'demo', label: 'code-1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// createItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should get an existing external public code repository', () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// } as Coderepo +// +// jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) +// +// const result = otomiStack.getCoderepo('1') +// expect(result).toEqual(coderepo) +// }) +// +// test('should edit an existing external public code repository', async () => { +// const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.editCoderepo('1', { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }) +// +// expect(coderepo).toEqual({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }) +// expect(updateItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// }, +// { id: '1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// updateItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should delete an existing external public code repository', async () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// } as Coderepo +// +// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) +// const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// await otomiStack.deleteCoderepo('1') +// +// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// deleteItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should create an external private code repository', async () => { +// const createItemSpy = jest.spyOn(otomiStack.db, 'createItem').mockReturnValue({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.createCoderepo('demo', { +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }) +// +// expect(coderepo).toEqual({ +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }) +// expect(createItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }, +// { teamId: 'demo', label: 'code-1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// createItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should get an existing external private code repository', () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// } as Coderepo +// +// jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) +// +// const result = otomiStack.getCoderepo('1') +// expect(result).toEqual(coderepo) +// }) +// +// test('should edit an existing external private code repository', async () => { +// const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// } as Coderepo) +// +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// const coderepo = await otomiStack.editCoderepo('1', { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }) +// +// expect(coderepo).toEqual({ +// id: '1', +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }) +// expect(updateItemSpy).toHaveBeenCalledWith( +// 'coderepos', +// { +// teamId: 'demo', +// label: 'code-1-updated', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// }, +// { id: '1' }, +// ) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// updateItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// +// test('should delete an existing external private code repository', async () => { +// const coderepo = { +// id: '1', +// teamId: 'demo', +// label: 'code-1', +// gitService: 'github', +// repositoryUrl: 'https://github.test.com', +// private: true, +// secret: 'test', +// } as Coderepo +// +// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) +// const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) +// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() +// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() +// +// await otomiStack.deleteCoderepo('1') +// +// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) +// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') +// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) +// +// deleteItemSpy.mockRestore() +// saveTeamCodereposSpy.mockRestore() +// doDeploymentSpy.mockRestore() +// }) +// }) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 8d2041edd..7cba5a447 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -7,7 +7,7 @@ import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' import { emptyDir, pathExists, unlink } from 'fs-extra' import { readdir, readFile, writeFile } from 'fs/promises' import { generate as generatePassword } from 'generate-password' -import { cloneDeep, filter, get, isArray, isEmpty, map, mapValues, omit, pick, set, unset } from 'lodash' +import { cloneDeep, filter, isEmpty, map, mapValues, omit, pick, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' import { cleanAllSessions, cleanSession, DbMessage, getIo, getSessionStack } from 'src/middleware' @@ -885,12 +885,11 @@ export default class OtomiStack { } getTeamCoderepos(teamId: string): Array { - const ids = { teamId } - return this.db.getCollection('coderepos', ids) as Array + return this.repoService.getTeamConfigService(teamId).getCoderepos() } getAllCoderepos(): Array { - const allCoderepos = this.db.getCollection('coderepos') as Array + const allCoderepos = this.repoService.getAllCoderepos() return allCoderepos } @@ -899,35 +898,33 @@ export default class OtomiStack { const body = { ...data } if (!body.private) unset(body, 'secret') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const coderepo = this.db.createItem('coderepos', { ...body, teamId }, { teamId, label: body.label }) as Coderepo - await this.saveTeamCoderepos(teamId) - await this.doDeployment(['coderepos']) + const coderepo = this.repoService.getTeamConfigService(teamId).createCoderepo({ ...data, teamId }) + await this.saveTeamCoderepo(teamId, coderepo) + await this.doDeployment(['coderepos'], teamId) return coderepo } catch (err) { - if (err.code === 409) err.publicMessage = 'Code repe label already exists' + if (err.code === 409) err.publicMessage = 'Code repo label already exists' throw err } } - getCoderepo(id: string): Coderepo { - return this.db.getItem('coderepos', { id }) as Coderepo + getCoderepo(teamId: string, id: string): Coderepo { + return this.repoService.getTeamConfigService(teamId).getCoderepo(id) } - async editCoderepo(id: string, data: Coderepo): Promise { + async editCoderepo(teamId: string, id: string, data: Coderepo): Promise { const body = { ...data } if (!body.private) unset(body, 'secret') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const coderepo = this.db.updateItem('coderepos', body, { id }) as Coderepo - await this.saveTeamCoderepos(coderepo.teamId as string) - await this.doDeployment(['coderepos']) + const coderepo = this.repoService.getTeamConfigService(teamId).updateCoderepo(id, body) + await this.saveTeamCoderepo(teamId, coderepo) + await this.doDeployment(['coderepos'], teamId) return coderepo } - async deleteCoderepo(id: string): Promise { - const coderepo = this.getCoderepo(id) - this.db.deleteItem('coderepos', { id }) - await this.saveTeamCoderepos(coderepo.teamId as string) - await this.doDeployment(['coderepos']) + async deleteCoderepo(teamId: string, id: string): Promise { + await this.deleteTeamCoderepo(teamId, id) + await this.doDeployment(['coderepos'], teamId) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -961,7 +958,7 @@ export default class OtomiStack { async getInternalRepoUrls(teamId: string): Promise { if (env.isDev || !teamId || teamId === 'admin') return [] const { cluster, otomi } = this.getSettings(['cluster', 'otomi']) - const gitea = this.getApp('admin', 'gitea') + const gitea = this.getApp('gitea') const username = gitea?.values?.adminUsername as string const password = (gitea?.values?.adminPassword as string) || (otomi?.adminPassword as string) const orgName = `team-${teamId}` @@ -1704,17 +1701,6 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async saveTeamCoderepos(teamId: string): Promise { - const coderepos = this.db.getCollection('coderepos', { teamId }) as Array - const cleaneProjects: Array> = coderepos.map((obj) => { - return omit(obj, ['teamId']) - }) - const relativePath = getTeamCodereposFilePath(teamId) - const outData: Record = set({}, getTeamCodereposJsonPath(teamId), cleaneProjects) - debug(`Saving coderepos of team: ${teamId}`) - await this.repo.writeFile(relativePath, outData) - } - async deleteTeamBuild(teamId: string, id: string): Promise { const build = this.repoService.getTeamConfigService(teamId).getBuild(id) this.repoService.getTeamConfigService(teamId).deleteBuild(id) @@ -1724,6 +1710,22 @@ export default class OtomiStack { await this.git.deleteConfig(repo, fileMap) } + async saveTeamCoderepo(teamId: string, coderepo: Coderepo): Promise { + debug(`Saving coderepo ${coderepo.label} for team ${teamId}`) + const repo = this.createTeamConfigInRepo(teamId, 'coderepos', coderepo) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCoderepo')! + await this.git.saveConfig(repo, fileMap) + } + + async deleteTeamCoderepo(teamId: string, id: string): Promise { + const coderepo = this.repoService.getTeamConfigService(teamId).getCoderepo(id) + this.repoService.getTeamConfigService(teamId).deleteCoderepo(id) + + const repo = this.createTeamConfigInRepo(teamId, 'coderepos', coderepo) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCoderepo')! + await this.git.deleteConfig(repo, fileMap) + } + async saveTeamPolicies(teamId: string): Promise { debug(`Saving team policies ${teamId}`) const policies = this.getTeamPolicies(teamId) diff --git a/src/repo.ts b/src/repo.ts index ab0a8a81c..609714b26 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -29,6 +29,7 @@ export interface FileMap { | 'AplSmtp' | 'AplBackupCollection' | 'AplUser' + | 'AplTeamCoderepo' | 'AplTeamBuild' | 'AplTeamPolicy' | 'AplTeamSettingSet' @@ -232,6 +233,15 @@ export function getFileMaps(envDir: string): Array { resourceGroup: 'platformSettings', resourceDir: 'settings', }, + { + kind: 'AplTeamCoderepo', + envDir, + jsonPathExpression: '$.teamConfig.*.coderepos[*]', + pathGlob: `${envDir}/env/teams/*/coderepos/*.yaml`, + processAs: 'arrayItem', + resourceGroup: 'team', + resourceDir: 'coderepos', + }, { kind: 'AplTeamBuild', envDir, diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 401314bb4..6e6e85148 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -4,6 +4,7 @@ import { Backup, Build, Cluster, + Coderepo, Dns, Ingress, Kms, @@ -127,6 +128,7 @@ export class RepoService { private getDefaultTeamConfig(): TeamConfig { return { builds: [], + coderepos: [], workloads: [], services: [], sealedSecrets: [], @@ -252,6 +254,10 @@ export class RepoService { return flatMap(this.repo.teamConfig, 'backups').filter(Boolean) ?? [] } + public getAllCoderepos(): Coderepo[] { + return flatMap(this.repo.teamConfig, 'coderepos').filter(Boolean) ?? [] + } + /** Retrieve a collection dynamically from the Repo */ public getCollection(collectionId: string): any { if (!has(this.repo, collectionId)) { diff --git a/src/services/TeamConfigService.test.ts b/src/services/TeamConfigService.test.ts index 5f45cbb23..43010eed0 100644 --- a/src/services/TeamConfigService.test.ts +++ b/src/services/TeamConfigService.test.ts @@ -25,6 +25,7 @@ describe('TeamConfigService', () => { beforeEach(() => { teamConfig = { builds: [], + coderepos: [], workloads: [], workloadValues: [], services: [], diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 20e5268b6..d54fb8296 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -2,6 +2,7 @@ import { App, Backup, Build, + Coderepo, Netpol, Policies, Policy, @@ -67,6 +68,42 @@ export class TeamConfigService { remove(this.teamConfig.builds, { id }) } + // ===================================== + // == CODEREPOS CRUD == + // ===================================== + + public createCoderepo(coderepo: Coderepo): Coderepo { + this.teamConfig.coderepos ??= [] + const newCoderepo = { ...coderepo, id: coderepo.id ?? uuidv4() } + if (find(this.teamConfig.coderepos, { name: newCoderepo.id })) { + throw new AlreadyExists(`Coderepo[${newCoderepo.id}] already exists.`) + } + this.teamConfig.coderepos.push(newCoderepo) + return newCoderepo + } + + public getCoderepo(id: string): Coderepo { + const coderepo = find(this.teamConfig.coderepos, { id }) + if (!coderepo) { + throw new NotExistError(`Coderepo[${id}] does not exist.`) + } + return coderepo + } + + public getCoderepos(): Coderepo[] { + return this.teamConfig.coderepos ?? [] + } + + public updateCoderepo(id: string, updates: Partial): Coderepo { + const coderepo = find(this.teamConfig.coderepos, { id }) + if (!coderepo) throw new NotExistError(`Coderepo[${id}] does not exist.`) + return merge(coderepo, updates) + } + + public deleteCoderepo(id: string): void { + remove(this.teamConfig.coderepos, { id }) + } + // ===================================== // == WORKLOADS CRUD == // ===================================== From 3c339f49613bec24a0ad4bb42572186736e8723c Mon Sep 17 00:00:00 2001 From: Jehoszafat Zimnowoda <17126497+j-zimnowoda@users.noreply.github.com> Date: Tue, 25 Feb 2025 10:23:47 +0100 Subject: [PATCH 16/49] fix: catch exception on status reporting interval --- src/app.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/app.ts b/src/app.ts index 70a47d9b2..6ab35b249 100644 --- a/src/app.ts +++ b/src/app.ts @@ -75,6 +75,10 @@ const checkAgainstGitea = async () => { const resourceStatus = async (errorSet) => { const otomiStack = await getSessionStack() + if (!otomiStack.isLoaded) { + debug('Values are not loaded yet') + return + } const { cluster } = otomiStack.getSettings(['cluster']) const domainSuffix = cluster?.domainSuffix const resources = { @@ -201,7 +205,11 @@ export async function initApp(inOtomiStack?: OtomiStack | undefined) { const emitResourceStatusInterval = 10 * 1000 const errorSet = new Set() setInterval(async function () { - await resourceStatus(errorSet) + try { + await resourceStatus(errorSet) + } catch (e) { + debug(e) + } }, emitResourceStatusInterval) // and register session middleware From 6a5463c9639ccaac847976dcba813d62398d3463 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 11:05:39 +0100 Subject: [PATCH 17/49] feat: set correctly enabled flag --- src/otomi-stack.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 7cba5a447..285e9b6ae 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -161,12 +161,18 @@ export default class OtomiStack { transformApps(appsObj: Record): App[] { if (!appsObj || typeof appsObj !== 'object') return [] - return Object.entries(appsObj).map(([appId, appData]) => ({ - id: appId, - enabled: appData.enabled ?? false, - values: omit(appData, ['enabled']), // Remove `enabled` from values - rawValues: {}, - })) + return Object.entries(appsObj).map(([appId, appData]) => { + // Retrieve schema to check if the `enabled` flag should be considered + const appSchema = getAppSchema(appId) + const isEnabled = appSchema?.properties?.enabled ? !!appData.enabled : undefined + + return { + id: appId, + enabled: isEnabled, + values: omit(appData, ['enabled']), + rawValues: {}, + } + }) } async initRepo(repoService?: RepoService): Promise { From 391e4ad7d9824bf406a597b51362396b21624033 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 11:33:55 +0100 Subject: [PATCH 18/49] feat: update getAll functions to contain teamId --- src/services/RepoService.ts | 50 ++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 6e6e85148..bcfc70c2b 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -25,7 +25,7 @@ import { Workload, } from '../otomi-models' import { TeamConfigService } from './TeamConfigService' -import { find, flatMap, has, map, mapValues, merge, remove, set } from 'lodash' +import { find, has, map, mapValues, merge, remove, set } from 'lodash' import { v4 as uuidv4 } from 'uuid' import { AlreadyExists } from '../error' @@ -223,15 +223,27 @@ export class RepoService { } public getAllNetpols(): Netpol[] { - return flatMap(this.repo.teamConfig, 'netpols').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.netpols.map((netpol) => ({ ...netpol, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllProjects(): Project[] { - return flatMap(this.repo.teamConfig, 'projects').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.projects.map((project) => ({ ...project, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllBuilds(): Build[] { - return flatMap(this.repo.teamConfig, 'builds').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.builds.map((build) => ({ ...build, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllPolicies(): Record { @@ -239,23 +251,43 @@ export class RepoService { } public getAllWorkloads(): Workload[] { - return flatMap(this.repo.teamConfig, 'workloads').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.workloads.map((workload) => ({ ...workload, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllServices(): Service[] { - return flatMap(this.repo.teamConfig, 'services').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.services.map((service) => ({ ...service, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllSealedSecrets(): SealedSecret[] { - return flatMap(this.repo.teamConfig, 'sealedSecrets').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.sealedSecrets.map((secret) => ({ ...secret, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllBackups(): Backup[] { - return flatMap(this.repo.teamConfig, 'backups').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.backups.map((backup) => ({ ...backup, teamId }))) + .filter(Boolean) ?? [] + ) } public getAllCoderepos(): Coderepo[] { - return flatMap(this.repo.teamConfig, 'coderepos').filter(Boolean) ?? [] + return ( + Object.entries(this.repo.teamConfig) + .flatMap(([teamId, config]) => config.coderepos.map((repo) => ({ ...repo, teamId }))) + .filter(Boolean) ?? [] + ) } /** Retrieve a collection dynamically from the Repo */ From 2a04567ea2c642a882bf72d51a55d08b321ee9b7 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 11:50:55 +0100 Subject: [PATCH 19/49] feat: update getAll functions to contain teamId --- src/services/RepoService.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index bcfc70c2b..c68f1c2ce 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -225,7 +225,7 @@ export class RepoService { public getAllNetpols(): Netpol[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.netpols.map((netpol) => ({ ...netpol, teamId }))) + .flatMap(([teamId, config]) => config.netpols?.map((netpol) => ({ ...netpol, teamId }))) .filter(Boolean) ?? [] ) } @@ -233,7 +233,7 @@ export class RepoService { public getAllProjects(): Project[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.projects.map((project) => ({ ...project, teamId }))) + .flatMap(([teamId, config]) => config.projects?.map((project) => ({ ...project, teamId }))) .filter(Boolean) ?? [] ) } @@ -241,7 +241,7 @@ export class RepoService { public getAllBuilds(): Build[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.builds.map((build) => ({ ...build, teamId }))) + .flatMap(([teamId, config]) => config.builds?.map((build) => ({ ...build, teamId }))) .filter(Boolean) ?? [] ) } @@ -253,7 +253,7 @@ export class RepoService { public getAllWorkloads(): Workload[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.workloads.map((workload) => ({ ...workload, teamId }))) + .flatMap(([teamId, config]) => config.workloads?.map((workload) => ({ ...workload, teamId }))) .filter(Boolean) ?? [] ) } @@ -261,7 +261,7 @@ export class RepoService { public getAllServices(): Service[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.services.map((service) => ({ ...service, teamId }))) + .flatMap(([teamId, config]) => config.services?.map((service) => ({ ...service, teamId }))) .filter(Boolean) ?? [] ) } @@ -269,7 +269,7 @@ export class RepoService { public getAllSealedSecrets(): SealedSecret[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.sealedSecrets.map((secret) => ({ ...secret, teamId }))) + .flatMap(([teamId, config]) => config.sealedSecrets?.map((secret) => ({ ...secret, teamId }))) .filter(Boolean) ?? [] ) } @@ -277,7 +277,7 @@ export class RepoService { public getAllBackups(): Backup[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.backups.map((backup) => ({ ...backup, teamId }))) + .flatMap(([teamId, config]) => config.backups?.map((backup) => ({ ...backup, teamId }))) .filter(Boolean) ?? [] ) } @@ -285,7 +285,7 @@ export class RepoService { public getAllCoderepos(): Coderepo[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.coderepos.map((repo) => ({ ...repo, teamId }))) + .flatMap(([teamId, config]) => config.coderepos?.map((repo) => ({ ...repo, teamId }))) .filter(Boolean) ?? [] ) } From 4d9bafb0e624d680a0b2f09e635c79ea580e14b6 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 14:08:49 +0100 Subject: [PATCH 20/49] feat: don't cleanup main session when error --- src/middleware/error.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/error.ts b/src/middleware/error.ts index e41618758..23088f113 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -28,6 +28,6 @@ export function errorMiddleware(e, req: OpenApiRequest, res: Response, next): vo msg = `Required property missing! '${requiredProperties}'` } const { otomi } = req as any - if (otomi?.sessionId) cleanSession(otomi.sessionId as string) + if (otomi?.sessionId !== 'main') cleanSession(otomi.sessionId as string) res.status(code).json({ error: msg }) } From df3fe8eead970a510ea1ef425392098998c07cdc Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 14:28:56 +0100 Subject: [PATCH 21/49] fix: not cleanup main session --- src/api.authz.test.ts | 1 + src/middleware/error.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index 893e16910..04b70bb94 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -41,6 +41,7 @@ describe('API authz tests', () => { const _otomiStack = await getSessionStack() _otomiStack.git = mockDeep() _otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) + _otomiStack.transformApps = jest.fn().mockReturnValue([]) await _otomiStack.initRepo() otomiStack = _otomiStack as jest.Mocked diff --git a/src/middleware/error.ts b/src/middleware/error.ts index 23088f113..cedaa3a93 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -28,6 +28,6 @@ export function errorMiddleware(e, req: OpenApiRequest, res: Response, next): vo msg = `Required property missing! '${requiredProperties}'` } const { otomi } = req as any - if (otomi?.sessionId !== 'main') cleanSession(otomi.sessionId as string) + if (otomi?.sessionId && otomi?.sessionId !== 'main') cleanSession(otomi.sessionId as string) res.status(code).json({ error: msg }) } From 414edb669cbe44ce14820a19951d26a54f005a2e Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 16:22:33 +0100 Subject: [PATCH 22/49] fix: codeRepo test and rename to codeRepo --- src/api.authz.test.ts | 26 +- src/api/coderepos.ts | 6 +- src/api/teams/{teamId}/coderepos.ts | 12 +- .../teams/{teamId}/coderepos/{codeRepoId}.ts | 37 + .../teams/{teamId}/coderepos/{coderepoId}.ts | 37 - src/middleware/jwt.test.ts | 2 + src/openapi/api.yaml | 54 +- src/openapi/{coderepo.yaml => codeRepo.yaml} | 2 +- src/otomi-models.ts | 4 +- src/otomi-stack.test.ts | 799 +++++++++--------- src/otomi-stack.ts | 62 +- src/repo.ts | 34 +- src/services/RepoService.ts | 8 +- src/services/TeamConfigService.test.ts | 2 +- src/services/TeamConfigService.ts | 42 +- ...epoUtils.test.ts => codeRepoUtils.test.ts} | 4 +- .../{coderepoUtils.ts => codeRepoUtils.ts} | 0 17 files changed, 553 insertions(+), 578 deletions(-) create mode 100644 src/api/teams/{teamId}/coderepos/{codeRepoId}.ts delete mode 100644 src/api/teams/{teamId}/coderepos/{coderepoId}.ts rename src/openapi/{coderepo.yaml => codeRepo.yaml} (98%) rename src/utils/{coderepoUtils.test.ts => codeRepoUtils.test.ts} (98%) rename src/utils/{coderepoUtils.ts => codeRepoUtils.ts} (100%) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index 04b70bb94..c9f07aa75 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -8,7 +8,7 @@ import OtomiStack from 'src/otomi-stack' import request, { SuperAgentTest } from 'supertest' import { HttpError } from './error' import { getSessionStack } from './middleware' -import { App, Coderepo, SealedSecret } from './otomi-models' +import { App, CodeRepo, SealedSecret } from './otomi-models' import * as getValuesSchemaModule from './utils' import { Git } from './git' @@ -644,8 +644,8 @@ describe('API authz tests', () => { private: true, secret: 'demo', } - test('team member can create its own coderepo', async () => { - jest.spyOn(otomiStack, 'createCoderepo').mockResolvedValue({} as Coderepo) + test('team member can create its own codeRepo', async () => { + jest.spyOn(otomiStack, 'createCodeRepo').mockResolvedValue({} as CodeRepo) await agent .post(`/v1/teams/${teamId}/coderepos`) .send(data) @@ -653,16 +653,16 @@ describe('API authz tests', () => { .expect(200) }) - test('team member can read its own coderepo', async () => { - jest.spyOn(otomiStack, 'getCoderepo').mockResolvedValue({} as never) + test('team member can read its own codeRepo', async () => { + jest.spyOn(otomiStack, 'getCodeRepo').mockResolvedValue({} as never) await agent .get(`/v1/teams/${teamId}/coderepos/my-uuid`) .set('Authorization', `Bearer ${teamMemberToken}`) .expect(200) }) - test('team member can update its own coderepo', async () => { - jest.spyOn(otomiStack, 'editCoderepo').mockResolvedValue({} as Coderepo) + test('team member can update its own codeRepo', async () => { + jest.spyOn(otomiStack, 'editCodeRepo').mockResolvedValue({} as CodeRepo) await agent .put(`/v1/teams/${teamId}/coderepos/my-uuid`) @@ -671,8 +671,8 @@ describe('API authz tests', () => { .expect(200) }) - test('team member can delete its own coderepo', async () => { - jest.spyOn(otomiStack, 'deleteCoderepo').mockResolvedValue() + test('team member can delete its own codeRepo', async () => { + jest.spyOn(otomiStack, 'deleteCodeRepo').mockResolvedValue() await agent .delete(`/v1/teams/${teamId}/coderepos/my-uuid`) @@ -682,7 +682,7 @@ describe('API authz tests', () => { .expect('Content-Type', /json/) }) - test('team member cannot create others coderepo', async () => { + test('team member cannot create others codeRepo', async () => { await agent .post(`/v1/teams/${otherTeamId}/coderepos`) .send(data) @@ -690,14 +690,14 @@ describe('API authz tests', () => { .expect(403) }) - test('team member cannot read others coderepo', async () => { + test('team member cannot read others codeRepo', async () => { await agent .get(`/v1/teams/${otherTeamId}/coderepos/my-uuid`) .set('Authorization', `Bearer ${teamMemberToken}`) .expect(403) }) - test('team member cannot update others coderepo', async () => { + test('team member cannot update others codeRepo', async () => { await agent .put(`/v1/teams/${otherTeamId}/coderepos/my-uuid`) .send(data) @@ -705,7 +705,7 @@ describe('API authz tests', () => { .expect(403) }) - test('team member cannot delete others coderepo', async () => { + test('team member cannot delete others codeRepo', async () => { await agent .delete(`/v1/teams/${otherTeamId}/coderepos/my-uuid`) .set('Content-Type', 'application/json') diff --git a/src/api/coderepos.ts b/src/api/coderepos.ts index b316fedd4..2f74422dc 100644 --- a/src/api/coderepos.ts +++ b/src/api/coderepos.ts @@ -2,13 +2,13 @@ import Debug from 'debug' import { Operation, OperationHandlerArray } from 'express-openapi' import { OpenApiRequestExt } from 'src/otomi-models' -const debug = Debug('otomi:api:coderepos') +const debug = Debug('otomi:api:codeRepos') export default function (): OperationHandlerArray { const get: Operation = [ ({ otomi }: OpenApiRequestExt, res): void => { - debug('getAllCoderepos') - const v = otomi.getAllCoderepos() + debug('getAllCodeRepos') + const v = otomi.getAllCodeRepos() res.json(v) }, ] diff --git a/src/api/teams/{teamId}/coderepos.ts b/src/api/teams/{teamId}/coderepos.ts index e60f3a0ed..d54f52d66 100644 --- a/src/api/teams/{teamId}/coderepos.ts +++ b/src/api/teams/{teamId}/coderepos.ts @@ -1,21 +1,21 @@ import Debug from 'debug' import { Operation, OperationHandlerArray } from 'express-openapi' -import { Coderepo, OpenApiRequestExt } from 'src/otomi-models' +import { CodeRepo, OpenApiRequestExt } from 'src/otomi-models' -const debug = Debug('otomi:api:teams:coderepos') +const debug = Debug('otomi:api:teams:codeRepos') export default function (): OperationHandlerArray { const get: Operation = [ ({ otomi, params: { teamId } }: OpenApiRequestExt, res): void => { - debug(`getTeamCoderepos(${teamId})`) - const v = otomi.getTeamCoderepos(teamId) + debug(`getTeamCodeRepos(${teamId})`) + const v = otomi.getTeamCodeRepos(teamId) res.json(v) }, ] const post: Operation = [ async ({ otomi, params: { teamId }, body }: OpenApiRequestExt, res): Promise => { - debug(`createCoderepos(${teamId}, ...)`) - const v = await otomi.createCoderepo(teamId, body as Coderepo) + debug(`createCodeRepos(${teamId}, ...)`) + const v = await otomi.createCodeRepo(teamId, body as CodeRepo) res.json(v) }, ] diff --git a/src/api/teams/{teamId}/coderepos/{codeRepoId}.ts b/src/api/teams/{teamId}/coderepos/{codeRepoId}.ts new file mode 100644 index 000000000..eec0d22fb --- /dev/null +++ b/src/api/teams/{teamId}/coderepos/{codeRepoId}.ts @@ -0,0 +1,37 @@ +import Debug from 'debug' +import { Operation, OperationHandlerArray } from 'express-openapi' +import { CodeRepo, OpenApiRequestExt } from 'src/otomi-models' + +const debug = Debug('otomi:api:teams:codeRepos') + +export default function (): OperationHandlerArray { + const del: Operation = [ + async ({ otomi, params: { teamId, codeRepoId } }: OpenApiRequestExt, res): Promise => { + debug(`deleteCodeRepo(${codeRepoId})`) + await otomi.deleteCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId)) + res.json({}) + }, + ] + const get: Operation = [ + ({ otomi, params: { teamId, codeRepoId } }: OpenApiRequestExt, res): void => { + debug(`getCodeRepo(${codeRepoId})`) + const data = otomi.getCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId)) + res.json(data) + }, + ] + const put: Operation = [ + async ({ otomi, params: { teamId, codeRepoId }, body }: OpenApiRequestExt, res): Promise => { + debug(`editCodeRepo(${codeRepoId})`) + const data = await otomi.editCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId), { + ...body, + } as CodeRepo) + res.json(data) + }, + ] + const api = { + delete: del, + get, + put, + } + return api +} diff --git a/src/api/teams/{teamId}/coderepos/{coderepoId}.ts b/src/api/teams/{teamId}/coderepos/{coderepoId}.ts deleted file mode 100644 index 489609519..000000000 --- a/src/api/teams/{teamId}/coderepos/{coderepoId}.ts +++ /dev/null @@ -1,37 +0,0 @@ -import Debug from 'debug' -import { Operation, OperationHandlerArray } from 'express-openapi' -import { Coderepo, OpenApiRequestExt } from 'src/otomi-models' - -const debug = Debug('otomi:api:teams:coderepos') - -export default function (): OperationHandlerArray { - const del: Operation = [ - async ({ otomi, params: { teamId, coderepoId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteCoderepo(${coderepoId})`) - await otomi.deleteCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId)) - res.json({}) - }, - ] - const get: Operation = [ - ({ otomi, params: { teamId, coderepoId } }: OpenApiRequestExt, res): void => { - debug(`getCoderepo(${coderepoId})`) - const data = otomi.getCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId)) - res.json(data) - }, - ] - const put: Operation = [ - async ({ otomi, params: { teamId, coderepoId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editCoderepo(${coderepoId})`) - const data = await otomi.editCoderepo(decodeURIComponent(teamId), decodeURIComponent(coderepoId), { - ...body, - } as Coderepo) - res.json(data) - }, - ] - const api = { - delete: del, - get, - put, - } - return api -} diff --git a/src/middleware/jwt.test.ts b/src/middleware/jwt.test.ts index ad5a76238..13ee7a6dd 100644 --- a/src/middleware/jwt.test.ts +++ b/src/middleware/jwt.test.ts @@ -34,6 +34,8 @@ describe('JWT claims mapping', () => { beforeEach(async () => { otomiStack = new OtomiStack() otomiStack.git = mockDeep() + jest.spyOn(otomiStack, 'transformApps').mockReturnValue([]) + await otomiStack.init() await otomiStack.loadValues() }) diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 433806162..72fa336a8 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -1147,9 +1147,9 @@ paths: /coderepos: get: - operationId: getAllCoderepos + operationId: getAllCodeRepos description: Get all code repositories - x-aclSchema: Coderepo + x-aclSchema: CodeRepo responses: '200': description: Successfully obtained all code repositories @@ -1158,7 +1158,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/Coderepo' + $ref: '#/components/schemas/CodeRepo' '400': <<: *BadRequest @@ -1166,9 +1166,9 @@ paths: parameters: - $ref: '#/components/parameters/teamParams' get: - operationId: getTeamCoderepos + operationId: getTeamCodeRepos description: Get code repos from a given team - x-aclSchema: Coderepo + x-aclSchema: CodeRepo responses: '200': description: Successfully obtained code repositories @@ -1177,7 +1177,7 @@ paths: schema: type: array items: - $ref: '#/components/schemas/Coderepo' + $ref: '#/components/schemas/CodeRepo' '400': description: Bad Request content: @@ -1185,9 +1185,9 @@ paths: schema: $ref: '#/components/schemas/OpenApiValidationError' post: - operationId: createCoderepo + operationId: createCodeRepo description: Create a team code repo - x-aclSchema: Coderepo + x-aclSchema: CodeRepo parameters: - name: teamId in: path @@ -1199,8 +1199,8 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Coderepo' - description: Coderepo object + $ref: '#/components/schemas/CodeRepo' + description: CodeRepo object required: true responses: <<: *DefaultPostResponses @@ -1209,16 +1209,16 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Coderepo' + $ref: '#/components/schemas/CodeRepo' - '/teams/{teamId}/coderepos/{coderepoId}': + '/teams/{teamId}/coderepos/{codeRepoId}': parameters: - $ref: '#/components/parameters/teamParams' - - $ref: '#/components/parameters/coderepoParams' + - $ref: '#/components/parameters/codeRepoParams' get: - operationId: getCoderepo + operationId: getCodeRepo description: Get a code repo from a given team - x-aclSchema: Coderepo + x-aclSchema: CodeRepo responses: <<: *DefaultGetResponses '200': @@ -1226,17 +1226,17 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Coderepo' + $ref: '#/components/schemas/CodeRepo' put: - operationId: editCoderepo + operationId: editCodeRepo description: Edit a code repo from a given team - x-aclSchema: Coderepo + x-aclSchema: CodeRepo requestBody: content: application/json: schema: - $ref: '#/components/schemas/Coderepo' - description: Coderepo object that contains updated values + $ref: '#/components/schemas/CodeRepo' + description: CodeRepo object that contains updated values required: true responses: <<: *DefaultGetResponses @@ -1245,11 +1245,11 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Coderepo' + $ref: '#/components/schemas/CodeRepo' delete: - operationId: deleteCoderepo + operationId: deleteCodeRepo description: Delete a code repo from a given team - x-aclSchema: Coderepo + x-aclSchema: CodeRepo responses: <<: *DefaultGetResponses '200': @@ -1718,8 +1718,8 @@ components: required: true schema: type: string - coderepoParams: - name: coderepoId + codeRepoParams: + name: codeRepoId in: path description: ID of the code repo required: true @@ -1796,8 +1796,8 @@ components: $ref: cloudtty.yaml#/Cloudtty Cluster: $ref: cluster.yaml#/Cluster - Coderepo: - $ref: coderepo.yaml#/Coderepo + CodeRepo: + $ref: codeRepo.yaml#/CodeRepo Ingress: $ref: service.yaml#/Ingress IngressCluster: diff --git a/src/openapi/coderepo.yaml b/src/openapi/codeRepo.yaml similarity index 98% rename from src/openapi/coderepo.yaml rename to src/openapi/codeRepo.yaml index 45d6d2412..ad3574446 100644 --- a/src/openapi/coderepo.yaml +++ b/src/openapi/codeRepo.yaml @@ -1,4 +1,4 @@ -Coderepo: +CodeRepo: x-acl: platformAdmin: - create-any diff --git a/src/otomi-models.ts b/src/otomi-models.ts index e7134b84d..496deebcd 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -28,7 +28,7 @@ export type Workload = components['schemas']['Workload'] export type WorkloadValues = components['schemas']['WorkloadValues'] export type User = components['schemas']['User'] export type Project = components['schemas']['Project'] -export type Coderepo = components['schemas']['Coderepo'] +export type CodeRepo = components['schemas']['CodeRepo'] export type Build = components['schemas']['Build'] export type Policy = components['schemas']['Policy'] export type Policies = components['schemas']['Policies'] @@ -172,7 +172,7 @@ export interface TeamConfig { apps: App[] backups: Backup[] builds: Build[] - coderepos: Coderepo[] + codeRepos: CodeRepo[] netpols: Netpol[] policies: Policies projects: Project[] diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index aa5ba1d3d..27dbd7ea9 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ -import { App, Policies, Team, TeamConfig, User } from 'src/otomi-models' +import { App, CodeRepo, Policies, Team, TeamConfig, User } from 'src/otomi-models' import OtomiStack from 'src/otomi-stack' import { mockDeep } from 'jest-mock-extended' import { PublicUrlExists } from './error' @@ -97,7 +97,7 @@ describe('Data validation', () => { test('should create a password when password is not specified', async () => { const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ builds: [], - coderepos: [], + codeRepos: [], workloads: [], services: [], sealedSecrets: [], @@ -117,7 +117,7 @@ describe('Data validation', () => { test('should not create a password when password is specified', async () => { const createItemSpy = jest.spyOn(otomiStack.repoService, 'createTeamConfig').mockReturnValue({ builds: [], - coderepos: [], + codeRepos: [], workloads: [], services: [], sealedSecrets: [], @@ -140,6 +140,8 @@ describe('Work with values', () => { let otomiStack: OtomiStack beforeEach(async () => { otomiStack = new OtomiStack() + jest.spyOn(otomiStack, 'transformApps').mockReturnValue([]) + await otomiStack.init() otomiStack.git = new Git('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() @@ -208,6 +210,7 @@ describe('Users tests', () => { }) jest.spyOn(otomiStack, 'saveUser').mockResolvedValue() jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'transformApps').mockReturnValue([]) jest.spyOn(otomiStack, 'getApp').mockReturnValue({ id: 'keycloak' }) await otomiStack.initRepo() await otomiStack.createUser(defaultPlatformAdmin) @@ -291,415 +294,381 @@ describe('Users tests', () => { }) }) -// describe('Code repositories tests', () => { -// let otomiStack: OtomiStack -// let teamConfigService: TeamConfigService -// -// beforeEach(async () => { -// otomiStack = new OtomiStack() -// await otomiStack.init() -// await otomiStack.initRepo() -// otomiStack.repoService.createTeamConfig('demo', { name: 'demo' }) -// teamConfigService = otomiStack.repoService.getTeamConfigService('demo') -// jest.spyOn(otomiStack, 'deleteTeamCoderepo').mockResolvedValue() -// }) -// -// afterEach(() => { -// jest.restoreAllMocks() -// }) -// -// test('should create an internal code repository', async () => { -// const createItemSpy = jest.spyOn(teamConfigService, 'createCoderepo').mockReturnValue({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.createCoderepo('demo', { -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }) -// -// expect(coderepo).toEqual({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }) -// expect(createItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }, -// { teamId: 'demo', label: 'code-1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// createItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should get an existing internal code repository', () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// } as Coderepo -// -// jest.spyOn(teamConfigService, 'getCoderepo').mockReturnValue(coderepo) -// -// const result = otomiStack.getCoderepo('demo', '1') -// expect(result).toEqual(coderepo) -// }) -// -// test('should edit an existing internal code repository', async () => { -// const updateItemSpy = jest.spyOn(teamConfigService, 'updateCoderepo').mockReturnValue({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.editCoderepo('demo', '1', { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }) -// -// expect(coderepo).toEqual({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }) -// expect(updateItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// }, -// { id: '1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// updateItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should delete an existing internal code repository', async () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'gitea', -// repositoryUrl: 'https://gitea.test.com', -// } as Coderepo -// -// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) -// const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCoderepo').mockResolvedValue({} as never) -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// await otomiStack.deleteCoderepo('demo', '1') -// -// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// deleteItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should create an external public code repository', async () => { -// const createItemSpy = jest.spyOn(teamConfigService, 'createCoderepo').mockReturnValue({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.createCoderepo('demo', { -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }) -// -// expect(coderepo).toEqual({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }) -// expect(createItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }, -// { teamId: 'demo', label: 'code-1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// createItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should get an existing external public code repository', () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// } as Coderepo -// -// jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) -// -// const result = otomiStack.getCoderepo('1') -// expect(result).toEqual(coderepo) -// }) -// -// test('should edit an existing external public code repository', async () => { -// const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.editCoderepo('1', { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }) -// -// expect(coderepo).toEqual({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }) -// expect(updateItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// }, -// { id: '1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// updateItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should delete an existing external public code repository', async () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// } as Coderepo -// -// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) -// const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// await otomiStack.deleteCoderepo('1') -// -// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// deleteItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should create an external private code repository', async () => { -// const createItemSpy = jest.spyOn(otomiStack.db, 'createItem').mockReturnValue({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.createCoderepo('demo', { -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }) -// -// expect(coderepo).toEqual({ -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }) -// expect(createItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }, -// { teamId: 'demo', label: 'code-1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// createItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should get an existing external private code repository', () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// } as Coderepo -// -// jest.spyOn(otomiStack.db, 'getItem').mockReturnValue(coderepo) -// -// const result = otomiStack.getCoderepo('1') -// expect(result).toEqual(coderepo) -// }) -// -// test('should edit an existing external private code repository', async () => { -// const updateItemSpy = jest.spyOn(otomiStack.db, 'updateItem').mockReturnValue({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// } as Coderepo) -// -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// const coderepo = await otomiStack.editCoderepo('1', { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }) -// -// expect(coderepo).toEqual({ -// id: '1', -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }) -// expect(updateItemSpy).toHaveBeenCalledWith( -// 'coderepos', -// { -// teamId: 'demo', -// label: 'code-1-updated', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// }, -// { id: '1' }, -// ) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// updateItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// -// test('should delete an existing external private code repository', async () => { -// const coderepo = { -// id: '1', -// teamId: 'demo', -// label: 'code-1', -// gitService: 'github', -// repositoryUrl: 'https://github.test.com', -// private: true, -// secret: 'test', -// } as Coderepo -// -// jest.spyOn(otomiStack, 'getCoderepo').mockReturnValue(coderepo) -// const deleteItemSpy = jest.spyOn(otomiStack.db, 'deleteItem').mockResolvedValue({} as never) -// const saveTeamCodereposSpy = jest.spyOn(otomiStack, 'saveTeamCoderepo').mockResolvedValue() -// const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() -// -// await otomiStack.deleteCoderepo('1') -// -// expect(deleteItemSpy).toHaveBeenCalledWith('coderepos', { id: '1' }) -// expect(saveTeamCodereposSpy).toHaveBeenCalledWith('demo') -// expect(doDeploymentSpy).toHaveBeenCalledWith(['coderepos']) -// -// deleteItemSpy.mockRestore() -// saveTeamCodereposSpy.mockRestore() -// doDeploymentSpy.mockRestore() -// }) -// }) +describe('Code repositories tests', () => { + let otomiStack: OtomiStack + let teamConfigService: TeamConfigService + + beforeEach(async () => { + otomiStack = new OtomiStack() + jest.spyOn(otomiStack, 'transformApps').mockReturnValue([]) + await otomiStack.init() + await otomiStack.initRepo() + otomiStack.git = mockDeep() + + try { + otomiStack.repoService.createTeamConfig('demo', { name: 'demo' }) + } catch { + // ignore + } + teamConfigService = otomiStack.repoService.getTeamConfigService('demo') + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + } as CodeRepo + + jest.spyOn(teamConfigService, 'getCodeRepo').mockReturnValue(codeRepo) + jest.spyOn(otomiStack.git, 'deleteConfig').mockResolvedValue() + }) + + afterEach(() => { + jest.restoreAllMocks() + }) + + test('should create an internal code repository', async () => { + const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.createCodeRepo('demo', { + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + + expect(codeRepo).toEqual({ + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + expect(createItemSpy).toHaveBeenCalledWith({ + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + createItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should get an existing internal code repository', () => { + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + } as CodeRepo + + jest.spyOn(teamConfigService, 'getCodeRepo').mockReturnValue(codeRepo) + + const result = otomiStack.getCodeRepo('demo', '1') + expect(result).toEqual(codeRepo) + }) + + test('should edit an existing internal code repository', async () => { + const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.editCodeRepo('demo', '1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + + expect(codeRepo).toEqual({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + expect(updateItemSpy).toHaveBeenCalledWith('1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + updateItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should delete an existing internal code repository', async () => { + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'gitea', + repositoryUrl: 'https://gitea.test.com', + } as CodeRepo + + jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) + const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + await otomiStack.deleteCodeRepo('demo', '1') + + expect(deleteItemSpy).toHaveBeenCalledWith('1') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + deleteItemSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should create an external public code repository', async () => { + const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.createCodeRepo('demo', { + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + + expect(codeRepo).toEqual({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + expect(createItemSpy).toHaveBeenCalledWith({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + createItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should get an existing external public code repository', () => { + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + } as CodeRepo + + jest.spyOn(teamConfigService, 'getCodeRepo').mockReturnValue(codeRepo) + + const result = otomiStack.getCodeRepo('demo', '1') + expect(result).toEqual(codeRepo) + }) + + test('should edit an existing external public code repository', async () => { + const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.editCodeRepo('demo', '1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + + expect(codeRepo).toEqual({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + expect(updateItemSpy).toHaveBeenCalledWith('1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + updateItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should delete an existing external public code repository', async () => { + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + } as CodeRepo + + jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) + const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + await otomiStack.deleteCodeRepo('demo', '1') + + expect(deleteItemSpy).toHaveBeenCalledWith('1') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + deleteItemSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should create an external private code repository', async () => { + const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.createCodeRepo('demo', { + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + + expect(codeRepo).toEqual({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + expect(createItemSpy).toHaveBeenCalledWith({ + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + createItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should edit an existing external private code repository', async () => { + const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + } as CodeRepo) + + const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + const codeRepo = await otomiStack.editCodeRepo('demo', '1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + + expect(codeRepo).toEqual({ + id: '1', + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + expect(updateItemSpy).toHaveBeenCalledWith('1', { + teamId: 'demo', + label: 'code-1-updated', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + }) + expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + updateItemSpy.mockRestore() + saveTeamCodeReposSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) + + test('should delete an existing external private code repository', async () => { + const codeRepo = { + id: '1', + teamId: 'demo', + label: 'code-1', + gitService: 'github', + repositoryUrl: 'https://github.test.com', + private: true, + secret: 'test', + } as CodeRepo + + jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) + const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) + const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + + await otomiStack.deleteCodeRepo('demo', '1') + + expect(deleteItemSpy).toHaveBeenCalledWith('1') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + + deleteItemSpy.mockRestore() + doDeploymentSpy.mockRestore() + }) +}) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 285e9b6ae..8edcb4167 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -16,7 +16,7 @@ import { Backup, Build, Cloudtty, - Coderepo, + CodeRepo, Core, K8sService, Netpol, @@ -76,7 +76,7 @@ import { normalizeRepoUrl, testPrivateRepoConnect, testPublicRepoConnect, -} from './utils/coderepoUtils' +} from './utils/codeRepoUtils' import { getPolicies } from './utils/policiesUtils' import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './utils/sealedSecretUtils' import { getKeycloakUsers, isValidUsername } from './utils/userUtils' @@ -890,47 +890,47 @@ export default class OtomiStack { await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } - getTeamCoderepos(teamId: string): Array { - return this.repoService.getTeamConfigService(teamId).getCoderepos() + getTeamCodeRepos(teamId: string): Array { + return this.repoService.getTeamConfigService(teamId).getCodeRepos() } - getAllCoderepos(): Array { - const allCoderepos = this.repoService.getAllCoderepos() - return allCoderepos + getAllCodeRepos(): Array { + const allCodeRepos = this.repoService.getAllCodeRepos() + return allCodeRepos } - async createCoderepo(teamId: string, data: Coderepo): Promise { + async createCodeRepo(teamId: string, data: CodeRepo): Promise { try { const body = { ...data } if (!body.private) unset(body, 'secret') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const coderepo = this.repoService.getTeamConfigService(teamId).createCoderepo({ ...data, teamId }) - await this.saveTeamCoderepo(teamId, coderepo) - await this.doDeployment(['coderepos'], teamId) - return coderepo + const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) + await this.saveTeamCodeRepo(teamId, codeRepo) + await this.doDeployment(['codeRepos'], teamId) + return codeRepo } catch (err) { if (err.code === 409) err.publicMessage = 'Code repo label already exists' throw err } } - getCoderepo(teamId: string, id: string): Coderepo { - return this.repoService.getTeamConfigService(teamId).getCoderepo(id) + getCodeRepo(teamId: string, id: string): CodeRepo { + return this.repoService.getTeamConfigService(teamId).getCodeRepo(id) } - async editCoderepo(teamId: string, id: string, data: Coderepo): Promise { + async editCodeRepo(teamId: string, id: string, data: CodeRepo): Promise { const body = { ...data } if (!body.private) unset(body, 'secret') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const coderepo = this.repoService.getTeamConfigService(teamId).updateCoderepo(id, body) - await this.saveTeamCoderepo(teamId, coderepo) - await this.doDeployment(['coderepos'], teamId) - return coderepo + const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(id, body) + await this.saveTeamCodeRepo(teamId, codeRepo) + await this.doDeployment(['codeRepos'], teamId) + return codeRepo } - async deleteCoderepo(teamId: string, id: string): Promise { - await this.deleteTeamCoderepo(teamId, id) - await this.doDeployment(['coderepos'], teamId) + async deleteCodeRepo(teamId: string, id: string): Promise { + await this.deleteTeamCodeRepo(teamId, id) + await this.doDeployment(['codeRepos'], teamId) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -1716,19 +1716,19 @@ export default class OtomiStack { await this.git.deleteConfig(repo, fileMap) } - async saveTeamCoderepo(teamId: string, coderepo: Coderepo): Promise { - debug(`Saving coderepo ${coderepo.label} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'coderepos', coderepo) - const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCoderepo')! + async saveTeamCodeRepo(teamId: string, codeRepo: CodeRepo): Promise { + debug(`Saving codeRepo ${codeRepo.label} for team ${teamId}`) + const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamCoderepo(teamId: string, id: string): Promise { - const coderepo = this.repoService.getTeamConfigService(teamId).getCoderepo(id) - this.repoService.getTeamConfigService(teamId).deleteCoderepo(id) + async deleteTeamCodeRepo(teamId: string, id: string): Promise { + const codeRepo = this.repoService.getTeamConfigService(teamId).getCodeRepo(id) + this.repoService.getTeamConfigService(teamId).deleteCodeRepo(id) - const repo = this.createTeamConfigInRepo(teamId, 'coderepos', coderepo) - const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCoderepo')! + const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) + const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! await this.git.deleteConfig(repo, fileMap) } diff --git a/src/repo.ts b/src/repo.ts index 609714b26..ae46fa076 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -4,7 +4,7 @@ import { pathExists } from 'fs-extra' import { rm, writeFile } from 'fs/promises' import { globSync } from 'glob' import jsonpath from 'jsonpath' -import { cloneDeep, get, merge, set } from 'lodash' +import { cloneDeep, get, merge, omit, set } from 'lodash' import path from 'path' import { getDirNames, loadYaml, objectToYaml } from './utils' @@ -29,7 +29,7 @@ export interface FileMap { | 'AplSmtp' | 'AplBackupCollection' | 'AplUser' - | 'AplTeamCoderepo' + | 'AplTeamCodeRepo' | 'AplTeamBuild' | 'AplTeamPolicy' | 'AplTeamSettingSet' @@ -234,13 +234,13 @@ export function getFileMaps(envDir: string): Array { resourceDir: 'settings', }, { - kind: 'AplTeamCoderepo', + kind: 'AplTeamCodeRepo', envDir, - jsonPathExpression: '$.teamConfig.*.coderepos[*]', - pathGlob: `${envDir}/env/teams/*/coderepos/*.yaml`, + jsonPathExpression: '$.teamConfig.*.codeRepos[*]', + pathGlob: `${envDir}/env/teams/*/codeRepos/*.yaml`, processAs: 'arrayItem', resourceGroup: 'team', - resourceDir: 'coderepos', + resourceDir: 'codeRepos', }, { kind: 'AplTeamBuild', @@ -349,15 +349,19 @@ export function hasCorrespondingDecryptedFile(filePath: string, fileList: Array< } export function renderManifest(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { - const manifest = { - kind: fileMap.kind, - metadata: { - name: getResourceName(fileMap, jsonPath, data), - labels: {}, - }, - spec: data, - } - if (fileMap.resourceGroup === 'team') { + //TODO remove this custom workaround for workloadValues + const manifest = + fileMap.kind === 'AplTeamWorkloadValues' + ? omit(data, ['id', 'name', 'teamId']) + : { + kind: fileMap.kind, + metadata: { + name: getResourceName(fileMap, jsonPath, data), + labels: {}, + }, + spec: data, + } + if (fileMap.resourceGroup === 'team' && fileMap.kind !== 'AplTeamWorkloadValues') { manifest.metadata.labels['apl.io/teamId'] = getTeamNameFromJsonPath(jsonPath) } diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index c68f1c2ce..231f628a0 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -4,7 +4,7 @@ import { Backup, Build, Cluster, - Coderepo, + CodeRepo, Dns, Ingress, Kms, @@ -128,7 +128,7 @@ export class RepoService { private getDefaultTeamConfig(): TeamConfig { return { builds: [], - coderepos: [], + codeRepos: [], workloads: [], services: [], sealedSecrets: [], @@ -282,10 +282,10 @@ export class RepoService { ) } - public getAllCoderepos(): Coderepo[] { + public getAllCodeRepos(): CodeRepo[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.coderepos?.map((repo) => ({ ...repo, teamId }))) + .flatMap(([teamId, config]) => config.codeRepos?.map((repo) => ({ ...repo, teamId }))) .filter(Boolean) ?? [] ) } diff --git a/src/services/TeamConfigService.test.ts b/src/services/TeamConfigService.test.ts index 43010eed0..4e59baa21 100644 --- a/src/services/TeamConfigService.test.ts +++ b/src/services/TeamConfigService.test.ts @@ -25,7 +25,7 @@ describe('TeamConfigService', () => { beforeEach(() => { teamConfig = { builds: [], - coderepos: [], + codeRepos: [], workloads: [], workloadValues: [], services: [], diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index d54fb8296..b4aa0c771 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -2,7 +2,7 @@ import { App, Backup, Build, - Coderepo, + CodeRepo, Netpol, Policies, Policy, @@ -72,36 +72,36 @@ export class TeamConfigService { // == CODEREPOS CRUD == // ===================================== - public createCoderepo(coderepo: Coderepo): Coderepo { - this.teamConfig.coderepos ??= [] - const newCoderepo = { ...coderepo, id: coderepo.id ?? uuidv4() } - if (find(this.teamConfig.coderepos, { name: newCoderepo.id })) { - throw new AlreadyExists(`Coderepo[${newCoderepo.id}] already exists.`) + public createCodeRepo(codeRepo: CodeRepo): CodeRepo { + this.teamConfig.codeRepos ??= [] + const newCodeRepo = { ...codeRepo, id: codeRepo.id ?? uuidv4() } + if (find(this.teamConfig.codeRepos, { name: newCodeRepo.id })) { + throw new AlreadyExists(`CodeRepo[${newCodeRepo.id}] already exists.`) } - this.teamConfig.coderepos.push(newCoderepo) - return newCoderepo + this.teamConfig.codeRepos.push(newCodeRepo) + return newCodeRepo } - public getCoderepo(id: string): Coderepo { - const coderepo = find(this.teamConfig.coderepos, { id }) - if (!coderepo) { - throw new NotExistError(`Coderepo[${id}] does not exist.`) + public getCodeRepo(id: string): CodeRepo { + const codeRepo = find(this.teamConfig.codeRepos, { id }) + if (!codeRepo) { + throw new NotExistError(`CodeRepo[${id}] does not exist.`) } - return coderepo + return codeRepo } - public getCoderepos(): Coderepo[] { - return this.teamConfig.coderepos ?? [] + public getCodeRepos(): CodeRepo[] { + return this.teamConfig.codeRepos ?? [] } - public updateCoderepo(id: string, updates: Partial): Coderepo { - const coderepo = find(this.teamConfig.coderepos, { id }) - if (!coderepo) throw new NotExistError(`Coderepo[${id}] does not exist.`) - return merge(coderepo, updates) + public updateCodeRepo(id: string, updates: Partial): CodeRepo { + const codeRepo = find(this.teamConfig.codeRepos, { id }) + if (!codeRepo) throw new NotExistError(`CodeRepo[${id}] does not exist.`) + return merge(codeRepo, updates) } - public deleteCoderepo(id: string): void { - remove(this.teamConfig.coderepos, { id }) + public deleteCodeRepo(id: string): void { + remove(this.teamConfig.codeRepos, { id }) } // ===================================== diff --git a/src/utils/coderepoUtils.test.ts b/src/utils/codeRepoUtils.test.ts similarity index 98% rename from src/utils/coderepoUtils.test.ts rename to src/utils/codeRepoUtils.test.ts index 9b6cd9837..efe0c967b 100644 --- a/src/utils/coderepoUtils.test.ts +++ b/src/utils/codeRepoUtils.test.ts @@ -4,7 +4,7 @@ import { chmod, writeFile } from 'fs/promises' import simpleGit, { SimpleGit } from 'simple-git' import { OtomiError } from 'src/error' import { v4 as uuidv4 } from 'uuid' -import { getGiteaRepoUrls, normalizeRepoUrl, testPrivateRepoConnect, testPublicRepoConnect } from './coderepoUtils' +import { getGiteaRepoUrls, normalizeRepoUrl, testPrivateRepoConnect, testPublicRepoConnect } from './codeRepoUtils' jest.mock('simple-git', () => ({ __esModule: true, @@ -27,7 +27,7 @@ jest.mock('src/error', () => ({ }), })) -describe('coderepoUtils', () => { +describe('codeRepoUtils', () => { beforeEach(() => { jest.clearAllMocks() }) diff --git a/src/utils/coderepoUtils.ts b/src/utils/codeRepoUtils.ts similarity index 100% rename from src/utils/coderepoUtils.ts rename to src/utils/codeRepoUtils.ts From 7efdade22bc23784a63857619f3002debde0f197 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 25 Feb 2025 16:44:27 +0100 Subject: [PATCH 23/49] fix: change object id's to name --- src/otomi-stack.ts | 219 ++++++++++++------------- src/services/TeamConfigService.test.ts | 40 ++--- src/services/TeamConfigService.ts | 124 +++++++------- 3 files changed, 191 insertions(+), 192 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 8edcb4167..ff3a3bed8 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -596,20 +596,20 @@ export default class OtomiStack { } } - getBackup(teamId: string, id: string): Backup { - return this.repoService.getTeamConfigService(teamId).getBackup(id) + getBackup(teamId: string, name: string): Backup { + return this.repoService.getTeamConfigService(teamId).getBackup(name) } - async editBackup(teamId: string, id: string, data: Backup): Promise { + async editBackup(teamId: string, name: string, data: Backup): Promise { validateBackupFields(data.name, data.ttl) - const backup = this.repoService.getTeamConfigService(teamId).updateBackup(id, data) + const backup = this.repoService.getTeamConfigService(teamId).updateBackup(name, data) await this.saveTeamBackup(teamId, data) await this.doDeployment(['backups'], teamId) return backup } - async deleteBackup(teamId: string, id: string): Promise { - await this.deleteTeamBackup(teamId, id) + async deleteBackup(teamId: string, name: string): Promise { + await this.deleteTeamBackup(teamId, name) await this.doDeployment(['backups'], teamId) } @@ -634,20 +634,20 @@ export default class OtomiStack { } } - getNetpol(teamId: string, id: string): Netpol { - return this.repoService.getTeamConfigService(teamId).getNetpol(id) + getNetpol(teamId: string, name: string): Netpol { + return this.repoService.getTeamConfigService(teamId).getNetpol(name) } - async editNetpol(teamId: string, id: string, data: Netpol): Promise { + async editNetpol(teamId: string, name: string, data: Netpol): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(id, data) + const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(name, data) await this.saveTeamNetpols(teamId, data) await this.doDeployment(['netpols'], teamId) return netpol } - async deleteNetpol(teamId: string, id: string): Promise { - const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(id) + async deleteNetpol(teamId: string, name: string): Promise { + const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) await this.deleteTeamNetpol(teamId, netpol.name) await this.doDeployment(['netpols'], teamId) } @@ -777,31 +777,31 @@ export default class OtomiStack { } } - getProject(teamId: string, id: string): Project { - const project = this.repoService.getTeamConfigService(teamId).getProject(id) + getProject(teamId: string, name: string): Project { + const project = this.repoService.getTeamConfigService(teamId).getProject(name) let build, workload, workloadValues, services try { - build = this.repoService.getTeamConfigService(teamId).getBuild(project.build!.id!) + build = this.repoService.getTeamConfigService(teamId).getBuild(project.build!.name) } catch (err) { build = {} } try { - workload = this.repoService.getTeamConfigService(teamId).getWorkload(project.workload!.id!) + workload = this.repoService.getTeamConfigService(teamId).getWorkload(project.workload!.name) } catch (err) { workload = {} } try { - workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(project.workloadValues!.id!) + workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(project.workloadValues!.name!) } catch (err) { workloadValues = {} } try { - services = this.repoService.getTeamConfigService(teamId).getService(project.service!.id!) + services = this.repoService.getTeamConfigService(teamId).getService(project.service!.name) } catch (err) { services = {} } return { - id, + id: name, teamId, ...project, name: project.name, @@ -812,38 +812,38 @@ export default class OtomiStack { } } - async editProject(teamId: string, id: string, data: Project): Promise { - const { build, workload, workloadValues, service, name } = data + async editProject(teamId: string, name: string, data: Project): Promise { + const { build, workload, workloadValues, service } = data let b, w, wv, s - if (!build?.id && build?.mode) { + if (!build?.name && build?.mode) { b = this.repoService.getTeamConfigService(teamId).createBuild({ ...build, teamId }) - } else if (build?.id) { - b = this.repoService.getTeamConfigService(teamId).updateBuild(build.id, build) + } else if (build?.name) { + b = this.repoService.getTeamConfigService(teamId).updateBuild(build.name, build) } - if (workload && !workload?.id) { + if (workload && !workload?.name) { w = this.repoService.getTeamConfigService(teamId).createWorkload(workload) - } else if (workload?.id) { - w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.id, workload) + } else if (workload?.name) { + w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.name, workload) } - if (workloadValues && !workloadValues?.id) { + if (workloadValues && !workloadValues?.name) { wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...workloadValues, name }) - } else if (workloadValues?.id) { + } else if (workloadValues?.name) { wv = this.repoService .getTeamConfigService(teamId) - .updateWorkloadValues(workloadValues.id, { ...workloadValues, name }) + .updateWorkloadValues(workloadValues.name, { ...workloadValues, name }) } - if (service && !service?.id) { + if (service && !service?.name) { s = this.repoService.getTeamConfigService(teamId).createService({ ...service, teamId }) - } else if (service?.id) { - s = this.repoService.getTeamConfigService(teamId).updateService(service.id, service) + } else if (service?.name) { + s = this.repoService.getTeamConfigService(teamId).updateService(service.name, service) } const updatedData = { - id, + id: name, name, teamId, ...(b && { build: { id: b.id } }), @@ -857,7 +857,7 @@ export default class OtomiStack { project = this.repoService.getTeamConfigService(teamId).createProject(updatedData) } catch (error) { if (error.code === 409) { - project = this.repoService.getTeamConfigService(teamId).updateProject(id, updatedData) + project = this.repoService.getTeamConfigService(teamId).updateProject(name, updatedData) } else { throw error } @@ -872,21 +872,21 @@ export default class OtomiStack { } // Deletes a project and all its related resources - async deleteProject(teamId: string, id: string): Promise { - const p = this.repoService.getTeamConfigService(teamId).getProject(id) - if (p.build?.id) { - await this.deleteTeamBuild(teamId, p.build.id) + async deleteProject(teamId: string, name: string): Promise { + const p = this.repoService.getTeamConfigService(teamId).getProject(name) + if (p.build?.name) { + await this.deleteTeamBuild(teamId, p.build.name) } - if (p.workload?.id) { - await this.deleteTeamWorkload(teamId, p.workload.id) + if (p.workload?.name) { + await this.deleteTeamWorkload(teamId, p.workload.name) } - if (p.workloadValues?.id) { - await this.deleteTeamWorkloadValues(teamId, p.workloadValues.id) + if (p.workloadValues?.name) { + await this.deleteTeamWorkloadValues(teamId, p.workloadValues.name) } - if (p.service?.id) { - await this.deleteTeamService(teamId, p.service.id) + if (p.service?.name) { + await this.deleteTeamService(teamId, p.service.name) } - await this.deleteTeamProject(teamId, id) + await this.deleteTeamProject(teamId, name) await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } @@ -1012,27 +1012,27 @@ export default class OtomiStack { } } - getBuild(teamId: string, id: string): Build { - return this.repoService.getTeamConfigService(teamId).getBuild(id) + getBuild(teamId: string, name: string): Build { + return this.repoService.getTeamConfigService(teamId).getBuild(name) } - async editBuild(teamId: string, id: string, data: Build): Promise { + async editBuild(teamId: string, name: string, data: Build): Promise { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const build = this.repoService.getTeamConfigService(teamId).updateBuild(id, data) + const build = this.repoService.getTeamConfigService(teamId).updateBuild(name, data) await this.saveTeamBuild(teamId, build) await this.doDeployment(['builds'], teamId) return build } - async deleteBuild(teamId: string, id: string): Promise { + async deleteBuild(teamId: string, name: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { - if (project?.build?.id === id) { + if (project?.build?.name === name) { const updatedData = { ...project, build: undefined } - this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) + this.repoService.getTeamConfigService(teamId).updateProject(project.name, updatedData) } }) - await this.deleteTeamBuild(teamId, id) + await this.deleteTeamBuild(teamId, name) await this.doDeployment(['builds'], teamId) } @@ -1167,7 +1167,6 @@ export default class OtomiStack { async createWorkload(teamId: string, data: Workload): Promise { try { const workload = this.repoService.getTeamConfigService(teamId).createWorkload({ ...data, teamId }) - console.log('workload', workload.id) const workloadValues = this.repoService .getTeamConfigService(teamId) .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) @@ -1181,38 +1180,38 @@ export default class OtomiStack { } } - getWorkload(teamId: string, id: string): Workload { - return this.repoService.getTeamConfigService(teamId).getWorkload(id) + getWorkload(teamId: string, name: string): Workload { + return this.repoService.getTeamConfigService(teamId).getWorkload(name) } - async editWorkload(teamId: string, id: string, data: Workload): Promise { - const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(id, data) + async editWorkload(teamId: string, name: string, data: Workload): Promise { + const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(name, data) await this.saveTeamWorkload(teamId, workload) await this.doDeployment(['workloads'], teamId) return workload } - async deleteWorkload(teamId: string, id: string): Promise { + async deleteWorkload(teamId: string, name: string): Promise { const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { - if (project?.workload?.id === id) { + if (project?.workload?.name === name) { const updatedData = { ...project, workload: undefined, workloadValues: undefined } - this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) + this.repoService.getTeamConfigService(teamId).updateProject(project.name, updatedData) } }) - await this.deleteTeamWorkloadValues(teamId, id) - await this.deleteTeamWorkload(teamId, id) + await this.deleteTeamWorkloadValues(teamId, name) + await this.deleteTeamWorkload(teamId, name) await this.doDeployment(['workloads', 'workloadValues'], teamId) } - async editWorkloadValues(teamId: string, id: string, data: WorkloadValues): Promise { + async editWorkloadValues(teamId: string, name: string, data: WorkloadValues): Promise { let workloadValues try { - workloadValues = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...data, id }) + workloadValues = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...data, name }) } catch (error) { if (error.code === 409) { debug('Workload values already exists, updating values') - workloadValues = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(id, data) + workloadValues = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(name, data) } } await this.saveTeamWorkloadValues(teamId, workloadValues) @@ -1220,8 +1219,8 @@ export default class OtomiStack { return workloadValues } - getWorkloadValues(teamId: string, id: string): WorkloadValues { - return this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) + getWorkloadValues(teamId: string, name: string): WorkloadValues { + return this.repoService.getTeamConfigService(teamId).getWorkloadValues(name) } getAllServices(): Array { @@ -1241,28 +1240,28 @@ export default class OtomiStack { } } - getService(teamId: string, id: string): Service { - return this.repoService.getTeamConfigService(teamId).getService(id) + getService(teamId: string, name: string): Service { + return this.repoService.getTeamConfigService(teamId).getService(name) } - async editService(teamId: string, id: string, data: Service): Promise { - const service = this.repoService.getTeamConfigService(teamId).updateService(id, data) + async editService(teamId: string, name: string, data: Service): Promise { + const service = this.repoService.getTeamConfigService(teamId).updateService(name, data) await this.saveTeamService(teamId, service) await this.doDeployment(['services'], teamId) return service } - async deleteService(teamId: string, id: string, deleteProjectService = true): Promise { + async deleteService(teamId: string, name: string, deleteProjectService = true): Promise { if (deleteProjectService) { const p = this.repoService.getTeamConfigService(teamId).getProjects() p.forEach((project: Project) => { - if (project?.service?.id === id) { + if (project?.service?.name === name) { const updatedData = { ...project, service: undefined } - this.repoService.getTeamConfigService(teamId).updateProject(project.id!, updatedData) + this.repoService.getTeamConfigService(teamId).updateProject(project.name, updatedData) } }) } - await this.deleteTeamService(teamId, id) + await this.deleteTeamService(teamId, name) await this.doDeployment(['services'], teamId) } @@ -1489,7 +1488,7 @@ export default class OtomiStack { .getTeamConfigService(teamId) .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) - await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.id!) + await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.name) await this.doDeployment(['sealedsecrets'], teamId) return sealedSecret } catch (err) { @@ -1498,7 +1497,7 @@ export default class OtomiStack { } } - async editSealedSecret(id: string, data: SealedSecret): Promise { + async editSealedSecret(name: string, data: SealedSecret): Promise { const namespace = data.namespace ?? `team-${data?.teamId}` const certificate = await getSealedSecretsCertificate() if (!certificate) { @@ -1516,21 +1515,21 @@ export default class OtomiStack { .getTeamConfigService(data.teamId!) .createSealedSecret({ ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) - await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, id) + await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, name) await this.doDeployment(['sealedsecrets'], data.teamId) return sealedSecret } - async deleteSealedSecret(teamId: string, id: string): Promise { - const sealedSecret = await this.getSealedSecret(teamId, id) - this.repoService.getTeamConfigService(teamId).deleteSealedSecret(id) - const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${id}.yaml`) + async deleteSealedSecret(teamId: string, name: string): Promise { + const sealedSecret = await this.getSealedSecret(teamId, name) + this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) + const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${name}.yaml`) await this.git.removeFile(relativePath) await this.doDeployment(['sealedsecrets'], teamId) } - async getSealedSecret(teamId: string, id: string): Promise { - const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(id) + async getSealedSecret(teamId: string, name: string): Promise { + const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(name) const namespace = sealedSecret.namespace ?? `team-${sealedSecret.teamId}` const secretValues = (await getSecretValues(sealedSecret.name, namespace)) || {} const isDisabled = isEmpty(secretValues) @@ -1621,8 +1620,8 @@ export default class OtomiStack { await this.git.removeDir(teamDir) } - async saveTeamSealedSecrets(teamId: string, data: any, id: string): Promise { - const relativePath = getTeamSealedSecretsValuesFilePath(teamId, `${id}.yaml`) + async saveTeamSealedSecrets(teamId: string, data: any, name: string): Promise { + const relativePath = getTeamSealedSecretsValuesFilePath(teamId, `${name}.yaml`) debug(`Saving sealed secrets of team: ${teamId}`) await this.git.writeFile(relativePath, data) } @@ -1643,9 +1642,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamBackup(teamId: string, id: string): Promise { - const backup = this.repoService.getTeamConfigService(teamId).getBackup(id) - this.repoService.getTeamConfigService(teamId).deleteBackup(id) + async deleteTeamBackup(teamId: string, name: string): Promise { + const backup = this.repoService.getTeamConfigService(teamId).getBackup(name) + this.repoService.getTeamConfigService(teamId).deleteBackup(name) const repo = this.createTeamConfigInRepo(teamId, 'backups', backup) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! @@ -1659,9 +1658,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamNetpol(teamId: string, id: string): Promise { - const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(id) - this.repoService.getTeamConfigService(teamId).deleteNetpol(id) + async deleteTeamNetpol(teamId: string, name: string): Promise { + const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) + this.repoService.getTeamConfigService(teamId).deleteNetpol(name) const repo = this.createTeamConfigInRepo(teamId, 'netpols', netpol) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! @@ -1675,9 +1674,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamWorkload(teamId: string, id: string): Promise { - const workload = this.repoService.getTeamConfigService(teamId).getWorkload(id) - this.repoService.getTeamConfigService(teamId).deleteWorkload(id) + async deleteTeamWorkload(teamId: string, name: string): Promise { + const workload = this.repoService.getTeamConfigService(teamId).getWorkload(name) + this.repoService.getTeamConfigService(teamId).deleteWorkload(name) const repo = this.createTeamConfigInRepo(teamId, 'workloads', workload) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! @@ -1691,9 +1690,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamProject(teamId: string, id: string): Promise { - const project = this.repoService.getTeamConfigService(teamId).getProject(id) - this.repoService.getTeamConfigService(teamId).deleteProject(id) + async deleteTeamProject(teamId: string, name: string): Promise { + const project = this.repoService.getTeamConfigService(teamId).getProject(name) + this.repoService.getTeamConfigService(teamId).deleteProject(name) const repo = this.createTeamConfigInRepo(teamId, 'projects', project) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! @@ -1707,9 +1706,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamBuild(teamId: string, id: string): Promise { - const build = this.repoService.getTeamConfigService(teamId).getBuild(id) - this.repoService.getTeamConfigService(teamId).deleteBuild(id) + async deleteTeamBuild(teamId: string, name: string): Promise { + const build = this.repoService.getTeamConfigService(teamId).getBuild(name) + this.repoService.getTeamConfigService(teamId).deleteBuild(name) const repo = this.createTeamConfigInRepo(teamId, 'builds', build) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! @@ -1743,7 +1742,7 @@ export default class OtomiStack { async saveTeamWorkloadValues(teamId: string, workloadValues: WorkloadValues): Promise { debug(`Saving workload values: ${workloadValues.id!} teamId: ${teamId} name: ${workloadValues.name}`) - const data = this.getWorkloadValues(teamId, workloadValues.id!) + const data = this.getWorkloadValues(teamId, workloadValues.name!) const updatedWorkloadValues = cloneDeep(data) as Record updatedWorkloadValues.values = stringifyYaml(data.values, undefined, 4) @@ -1752,9 +1751,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamWorkloadValues(teamId: string, id: string): Promise { - const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(id) - this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(id) + async deleteTeamWorkloadValues(teamId: string, name: string): Promise { + const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(name) + this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(name) const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', workloadValues) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! @@ -1768,9 +1767,9 @@ export default class OtomiStack { await this.git.saveConfig(repo, fileMap) } - async deleteTeamService(teamId: string, id: string): Promise { - const service = this.repoService.getTeamConfigService(teamId).getService(id) - this.repoService.getTeamConfigService(teamId).deleteService(id) + async deleteTeamService(teamId: string, name: string): Promise { + const service = this.repoService.getTeamConfigService(teamId).getService(name) + this.repoService.getTeamConfigService(teamId).deleteService(name) const repo = this.createTeamConfigInRepo(teamId, 'services', service) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! diff --git a/src/services/TeamConfigService.test.ts b/src/services/TeamConfigService.test.ts index 4e59baa21..916b3c448 100644 --- a/src/services/TeamConfigService.test.ts +++ b/src/services/TeamConfigService.test.ts @@ -58,7 +58,7 @@ describe('TeamConfigService', () => { test('should retrieve a build by id', () => { const createdBuild = service.createBuild(build) - expect(service.getBuild(createdBuild.id!)).toEqual(createdBuild) + expect(service.getBuild(createdBuild.name)).toEqual(createdBuild) }) test('should throw an error when retrieving a non-existent build', () => { @@ -68,14 +68,14 @@ describe('TeamConfigService', () => { test('should update a build', () => { const createdBuild = service.createBuild(build) - const updatedBuild = service.updateBuild(createdBuild.id!, { name: 'UpdatedBuild' }) + const updatedBuild = service.updateBuild(createdBuild.name, { name: 'UpdatedBuild' }) expect(updatedBuild.name).toBe('UpdatedBuild') }) test('should delete a build', () => { const createdBuild = service.createBuild(build) - service.deleteBuild(createdBuild.id!) + service.deleteBuild(createdBuild.name) expect(service.getBuilds()).toHaveLength(0) }) }) @@ -98,7 +98,7 @@ describe('TeamConfigService', () => { test('should retrieve a workload by id', () => { const createdWorkload = service.createWorkload(workload) - expect(service.getWorkload(createdWorkload.id!)).toEqual(createdWorkload) + expect(service.getWorkload(createdWorkload.name)).toEqual(createdWorkload) }) test('should throw an error when retrieving a non-existent workload', () => { @@ -108,7 +108,7 @@ describe('TeamConfigService', () => { test('should delete a workload', () => { const createdWorkload = service.createWorkload(workload) - service.deleteWorkload(createdWorkload.id!) + service.deleteWorkload(createdWorkload.name) expect(service.getWorkloads()).toHaveLength(0) }) }) @@ -131,7 +131,7 @@ describe('TeamConfigService', () => { test('should retrieve a service by id', () => { const createdService = service.createService(serviceData) - expect(service.getService(createdService.id!)).toEqual(createdService) + expect(service.getService(createdService.name)).toEqual(createdService) }) test('should throw an error when retrieving a non-existent service', () => { @@ -173,7 +173,7 @@ describe('TeamConfigService', () => { test('should retrieve a sealed secret by id', () => { const createdSecret = service.createSealedSecret(secret) - expect(service.getSealedSecret(createdSecret.id!)).toEqual(createdSecret) + expect(service.getSealedSecret(createdSecret.name)).toEqual(createdSecret) }) test('should throw an error when retrieving a non-existent sealed secret', () => { @@ -183,7 +183,7 @@ describe('TeamConfigService', () => { test('should delete a sealed secret', () => { const createdSecret = service.createSealedSecret(secret) - service.deleteSealedSecret(createdSecret.id!) + service.deleteSealedSecret(createdSecret.name) expect(service.getSealedSecrets()).toHaveLength(0) }) }) @@ -194,7 +194,7 @@ describe('TeamConfigService', () => { test('should create workload values', () => { const created = service.createWorkloadValues(workloadValues) expect(created).toEqual({ name: 'TestWorkloadValues', id: 'mocked-uuid', values: { test: 'values' } }) - expect(service.getWorkloadValues(created.id!)).toEqual(created) + expect(service.getWorkloadValues(created.name!)).toEqual(created) }) test('should throw an error when creating duplicate workload values', () => { @@ -204,8 +204,8 @@ describe('TeamConfigService', () => { test('should delete workload values', () => { const created = service.createWorkloadValues(workloadValues) - service.deleteWorkloadValues(created.id!) - expect(() => service.getWorkloadValues(created.id!)).toThrow(NotExistError) + service.deleteWorkloadValues(created.name!) + expect(() => service.getWorkloadValues(created.name!)).toThrow(NotExistError) }) }) @@ -215,7 +215,7 @@ describe('TeamConfigService', () => { test('should create a backup', () => { const created = service.createBackup(backup) expect(created).toEqual({ name: 'TestBackup', id: 'mocked-uuid', ttl: '1', schedule: '0 0 * * *' }) - expect(service.getBackup(created.id!)).toEqual(created) + expect(service.getBackup(created.name)).toEqual(created) }) test('should throw an error when creating duplicate backup', () => { @@ -225,8 +225,8 @@ describe('TeamConfigService', () => { test('should delete a backup', () => { const created = service.createBackup(backup) - service.deleteBackup(created.id!) - expect(() => service.getBackup(created.id!)).toThrow(NotExistError) + service.deleteBackup(created.name) + expect(() => service.getBackup(created.name)).toThrow(NotExistError) }) }) @@ -236,7 +236,7 @@ describe('TeamConfigService', () => { test('should create a project', () => { const created = service.createProject(project) expect(created).toEqual({ name: 'TestProject', id: 'mocked-uuid' }) - expect(service.getProject(created.id!)).toEqual(created) + expect(service.getProject(created.name)).toEqual(created) }) test('should throw an error when creating duplicate project', () => { @@ -246,8 +246,8 @@ describe('TeamConfigService', () => { test('should delete a project', () => { const created = service.createProject(project) - service.deleteProject(created.id!) - expect(() => service.getProject(created.id!)).toThrow(NotExistError) + service.deleteProject(created.name) + expect(() => service.getProject(created.name)).toThrow(NotExistError) }) }) @@ -257,7 +257,7 @@ describe('TeamConfigService', () => { test('should create a netpol', () => { const created = service.createNetpol(netpol) expect(created).toEqual({ name: 'TestNetpol', id: 'mocked-uuid' }) - expect(service.getNetpol(created.id!)).toEqual(created) + expect(service.getNetpol(created.name)).toEqual(created) }) test('should throw an error when creating duplicate netpol', () => { @@ -267,8 +267,8 @@ describe('TeamConfigService', () => { test('should delete a netpol', () => { const created = service.createNetpol(netpol) - service.deleteNetpol(created.id!) - expect(() => service.getNetpol(created.id!)).toThrow(NotExistError) + service.deleteNetpol(created.name) + expect(() => service.getNetpol(created.name)).toThrow(NotExistError) }) }) diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index b4aa0c771..b67f33abc 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -46,10 +46,10 @@ export class TeamConfigService { return newBuild } - public getBuild(id: string): Build { - const build = find(this.teamConfig.builds, { id }) + public getBuild(name: string): Build { + const build = find(this.teamConfig.builds, { name }) if (!build) { - throw new NotExistError(`Build[${id}] does not exist.`) + throw new NotExistError(`Build[${name}] does not exist.`) } return build } @@ -58,14 +58,14 @@ export class TeamConfigService { return this.teamConfig.builds ?? [] } - public updateBuild(id: string, updates: Partial): Build { - const build = find(this.teamConfig.builds, { id }) - if (!build) throw new NotExistError(`Build[${id}] does not exist.`) + public updateBuild(name: string, updates: Partial): Build { + const build = find(this.teamConfig.builds, { name }) + if (!build) throw new NotExistError(`Build[${name}] does not exist.`) return merge(build, updates) } - public deleteBuild(id: string): void { - remove(this.teamConfig.builds, { id }) + public deleteBuild(name: string): void { + remove(this.teamConfig.builds, { name }) } // ===================================== @@ -119,10 +119,10 @@ export class TeamConfigService { return newWorkload } - public getWorkload(id: string): Workload { - const workload = find(this.teamConfig.workloads, { id }) + public getWorkload(name: string): Workload { + const workload = find(this.teamConfig.workloads, { name }) if (!workload) { - throw new NotExistError(`Workload[${id}] does not exist.`) + throw new NotExistError(`Workload[${name}] does not exist.`) } return workload } @@ -131,14 +131,14 @@ export class TeamConfigService { return this.teamConfig.workloads ?? [] } - public updateWorkload(id: string, updates: Partial): Workload { - const workload = find(this.teamConfig.workloads, { id }) - if (!workload) throw new NotExistError(`Workload[${id}] does not exist.`) + public updateWorkload(name: string, updates: Partial): Workload { + const workload = find(this.teamConfig.workloads, { name }) + if (!workload) throw new NotExistError(`Workload[${name}] does not exist.`) return merge(workload, updates) } - public deleteWorkload(id: string): void { - remove(this.teamConfig.workloads, { id }) + public deleteWorkload(name: string): void { + remove(this.teamConfig.workloads, { name }) } // ===================================== @@ -158,22 +158,22 @@ export class TeamConfigService { return newWorkloadValues } - public getWorkloadValues(id: string): WorkloadValues { - const workloadValues = find(this.teamConfig.workloadValues, { id }) + public getWorkloadValues(name: string): WorkloadValues { + const workloadValues = find(this.teamConfig.workloadValues, { name }) if (!workloadValues) { - throw new NotExistError(`WorkloadValues[${id}] does not exist.`) + throw new NotExistError(`WorkloadValues[${name}] does not exist.`) } return workloadValues } - public updateWorkloadValues(id: string, updates: Partial): WorkloadValues { - const workloadValues = find(this.teamConfig.workloadValues, { id }) - if (!workloadValues) throw new NotExistError(`WorkloadValues[${id}] does not exist.`) + public updateWorkloadValues(name: string, updates: Partial): WorkloadValues { + const workloadValues = find(this.teamConfig.workloadValues, { name }) + if (!workloadValues) throw new NotExistError(`WorkloadValues[${name}] does not exist.`) return merge(workloadValues, updates) } - public deleteWorkloadValues(id: string): void { - remove(this.teamConfig.workloadValues, { id }) + public deleteWorkloadValues(name: string): void { + remove(this.teamConfig.workloadValues, { name }) } // ===================================== @@ -190,10 +190,10 @@ export class TeamConfigService { return newService } - public getService(id: string): Service { - const service = find(this.teamConfig.services, { id }) + public getService(name: string): Service { + const service = find(this.teamConfig.services, { name }) if (!service) { - throw new NotExistError(`Service[${id}] does not exist.`) + throw new NotExistError(`Service[${name}] does not exist.`) } return service } @@ -201,9 +201,9 @@ export class TeamConfigService { public getServices(): Service[] { return this.teamConfig.services ?? [] } - public updateService(id: string, updates: Partial): Service { - const service = find(this.teamConfig.services, { id }) - if (!service) throw new NotExistError(`Service[${id}] does not exist.`) + public updateService(name: string, updates: Partial): Service { + const service = find(this.teamConfig.services, { name }) + if (!service) throw new NotExistError(`Service[${name}] does not exist.`) return merge(service, updates) } @@ -225,10 +225,10 @@ export class TeamConfigService { return newSecret } - public getSealedSecret(id: string): SealedSecret { - const sealedSecrets = find(this.teamConfig.sealedSecrets, { id }) + public getSealedSecret(name: string): SealedSecret { + const sealedSecrets = find(this.teamConfig.sealedSecrets, { name }) if (!sealedSecrets) { - throw new NotExistError(`SealedSecret[${id}] does not exist.`) + throw new NotExistError(`SealedSecret[${name}] does not exist.`) } return sealedSecrets } @@ -237,14 +237,14 @@ export class TeamConfigService { return this.teamConfig.sealedSecrets ?? [] } - public updateSealedSecret(id: string, updates: Partial): SealedSecret { - const secret = find(this.teamConfig.sealedSecrets, { id }) - if (!secret) throw new NotExistError(`SealedSecret[${id}] does not exist.`) + public updateSealedSecret(name: string, updates: Partial): SealedSecret { + const secret = find(this.teamConfig.sealedSecrets, { name }) + if (!secret) throw new NotExistError(`SealedSecret[${name}] does not exist.`) return merge(secret, updates) } - public deleteSealedSecret(id: string): void { - remove(this.teamConfig.sealedSecrets, { id }) + public deleteSealedSecret(name: string): void { + remove(this.teamConfig.sealedSecrets, { name }) } // ===================================== @@ -261,10 +261,10 @@ export class TeamConfigService { return newBackup } - public getBackup(id: string): Backup { - const backup = find(this.teamConfig.backups, { id }) + public getBackup(name: string): Backup { + const backup = find(this.teamConfig.backups, { name }) if (!backup) { - throw new NotExistError(`Backup[${id}] does not exist.`) + throw new NotExistError(`Backup[${name}] does not exist.`) } return backup } @@ -273,14 +273,14 @@ export class TeamConfigService { return this.teamConfig.backups ?? [] } - public updateBackup(id: string, updates: Partial): Backup { - const backup = find(this.teamConfig.backups, { id }) - if (!backup) throw new NotExistError(`Backup[${id}] does not exist.`) + public updateBackup(name: string, updates: Partial): Backup { + const backup = find(this.teamConfig.backups, { name }) + if (!backup) throw new NotExistError(`Backup[${name}] does not exist.`) return merge(backup, updates) } - public deleteBackup(id: string): void { - remove(this.teamConfig.backups, { id }) + public deleteBackup(name: string): void { + remove(this.teamConfig.backups, { name }) } // ===================================== @@ -297,10 +297,10 @@ export class TeamConfigService { return newProject } - public getProject(id: string): Project { - const project = find(this.teamConfig.projects, { id }) + public getProject(name: string): Project { + const project = find(this.teamConfig.projects, { name }) if (!project) { - throw new NotExistError(`Project[${id}] does not exist.`) + throw new NotExistError(`Project[${name}] does not exist.`) } return project } @@ -309,14 +309,14 @@ export class TeamConfigService { return this.teamConfig.projects ?? [] } - public updateProject(id: string, updates: Partial): Project { - const project = find(this.teamConfig.projects, { id }) - if (!project) throw new NotExistError(`Project[${id}] does not exist.`) + public updateProject(name: string, updates: Partial): Project { + const project = find(this.teamConfig.projects, { name }) + if (!project) throw new NotExistError(`Project[${name}] does not exist.`) return merge(project, updates) } - public deleteProject(id: string): void { - remove(this.teamConfig.projects, { id }) + public deleteProject(name: string): void { + remove(this.teamConfig.projects, { name }) } // ===================================== @@ -333,10 +333,10 @@ export class TeamConfigService { return newNetpol } - public getNetpol(id: string): Netpol { - const netpol = find(this.teamConfig.netpols, { id }) + public getNetpol(name: string): Netpol { + const netpol = find(this.teamConfig.netpols, { name }) if (!netpol) { - throw new NotExistError(`Netpol[${id}] does not exist.`) + throw new NotExistError(`Netpol[${name}] does not exist.`) } return netpol } @@ -345,16 +345,16 @@ export class TeamConfigService { return this.teamConfig.netpols ?? [] } - public updateNetpol(id: string, updates: Partial): Netpol { - const netpol = find(this.teamConfig.netpols, { id }) + public updateNetpol(name: string, updates: Partial): Netpol { + const netpol = find(this.teamConfig.netpols, { name }) if (!netpol) { - throw new NotExistError(`Netpol[${id}] does not exist.`) + throw new NotExistError(`Netpol[${name}] does not exist.`) } return merge(netpol, updates) } - public deleteNetpol(id: string): void { - remove(this.teamConfig.netpols, { id }) + public deleteNetpol(name: string): void { + remove(this.teamConfig.netpols, { name }) } // ===================================== From b844eb253e59781f0e645fb0629b44b3b50c82ed Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 27 Feb 2025 13:15:52 +0100 Subject: [PATCH 24/49] fix: workloadValues --- src/repo.ts | 8 ++++- src/services/TeamConfigService.ts | 55 ++++++++++++++++++++++++++----- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/repo.ts b/src/repo.ts index ae46fa076..b9cadbabe 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -477,7 +477,13 @@ export async function loadFileToSpec( const data = await deps.loadYaml(filePath) if (fileMap.processAs === 'arrayItem') { const ref: Record[] = get(spec, jsonPath) - ref.push(data?.spec) + //TODO remove this custom workaround for workloadValues as it has no spec + if (fileMap.kind === 'AplTeamWorkloadValues') { + const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] + ref.push({ ...data, name }) + } else { + ref.push(data?.spec) + } } else { const ref: Record = get(spec, jsonPath) // Decrypted secrets may need to be merged with plain text specs diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index b67f33abc..bf055c9f6 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -55,7 +55,11 @@ export class TeamConfigService { } public getBuilds(): Build[] { - return this.teamConfig.builds ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.builds ?? []).map((build) => ({ + ...build, + teamId, + })) } public updateBuild(name: string, updates: Partial): Build { @@ -91,7 +95,11 @@ export class TeamConfigService { } public getCodeRepos(): CodeRepo[] { - return this.teamConfig.codeRepos ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.codeRepos ?? []).map((codeRepo) => ({ + ...codeRepo, + teamId, + })) } public updateCodeRepo(id: string, updates: Partial): CodeRepo { @@ -128,7 +136,11 @@ export class TeamConfigService { } public getWorkloads(): Workload[] { - return this.teamConfig.workloads ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.workloads ?? []).map((workloadValues) => ({ + ...workloadValues, + teamId, + })) } public updateWorkload(name: string, updates: Partial): Workload { @@ -199,8 +211,13 @@ export class TeamConfigService { } public getServices(): Service[] { - return this.teamConfig.services ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.services ?? []).map((service) => ({ + ...service, + teamId, + })) } + public updateService(name: string, updates: Partial): Service { const service = find(this.teamConfig.services, { name }) if (!service) throw new NotExistError(`Service[${name}] does not exist.`) @@ -234,7 +251,11 @@ export class TeamConfigService { } public getSealedSecrets(): SealedSecret[] { - return this.teamConfig.sealedSecrets ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.sealedSecrets ?? []).map((sealedSecret) => ({ + ...sealedSecret, + teamId, + })) } public updateSealedSecret(name: string, updates: Partial): SealedSecret { @@ -270,7 +291,11 @@ export class TeamConfigService { } public getBackups(): Backup[] { - return this.teamConfig.backups ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.backups ?? []).map((backup) => ({ + ...backup, + teamId, + })) } public updateBackup(name: string, updates: Partial): Backup { @@ -306,7 +331,11 @@ export class TeamConfigService { } public getProjects(): Project[] { - return this.teamConfig.projects ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.projects ?? []).map((project) => ({ + ...project, + teamId, + })) } public updateProject(name: string, updates: Partial): Project { @@ -342,7 +371,11 @@ export class TeamConfigService { } public getNetpols(): Netpol[] { - return this.teamConfig.netpols ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.netpols ?? []).map((netpol) => ({ + ...netpol, + teamId, + })) } public updateNetpol(name: string, updates: Partial): Netpol { @@ -395,7 +428,11 @@ export class TeamConfigService { } public getApps(): App[] { - return this.teamConfig.apps ?? [] + const teamId = this.teamConfig.settings?.id + return (this.teamConfig.apps ?? []).map((app) => ({ + ...app, + teamId, + })) } // ===================================== From 7a2bce340ff16b8b02df75d81bfd210436c80ab2 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Thu, 27 Feb 2025 17:08:16 +0100 Subject: [PATCH 25/49] feat: convert resource ids to names --- .../{{backupId}.ts => {backupName}.ts} | 18 +-- .../builds/{{buildId}.ts => {buildName}.ts} | 18 +-- ...codeRepoId}.ts => {codeRepositoryName}.ts} | 18 +-- .../{{netpolId}.ts => {netpolName}.ts} | 18 +-- .../{{projectId}.ts => {projectName}.ts} | 18 +-- .../{{secretId}.ts => {sealedSecretName}.ts} | 18 +-- .../{{serviceId}.ts => {serviceName}.ts} | 18 +-- .../{{workloadId}.ts => {workloadName}.ts} | 18 +-- .../values.ts | 12 +- src/openapi/api.yaml | 88 ++++----------- src/openapi/codeRepo.yaml | 4 +- src/openapi/settingsinfo.yaml | 4 + src/otomi-models.ts | 2 +- src/otomi-stack.ts | 106 +++++++++++------- src/repo.ts | 3 + src/services/TeamConfigService.ts | 47 ++++---- 16 files changed, 200 insertions(+), 210 deletions(-) rename src/api/teams/{teamId}/backups/{{backupId}.ts => {backupName}.ts} (58%) rename src/api/teams/{teamId}/builds/{{buildId}.ts => {buildName}.ts} (60%) rename src/api/teams/{teamId}/coderepos/{{codeRepoId}.ts => {codeRepositoryName}.ts} (55%) rename src/api/teams/{teamId}/netpols/{{netpolId}.ts => {netpolName}.ts} (58%) rename src/api/teams/{teamId}/projects/{{projectId}.ts => {projectName}.ts} (59%) rename src/api/teams/{teamId}/sealedsecrets/{{secretId}.ts => {sealedSecretName}.ts} (57%) rename src/api/teams/{teamId}/services/{{serviceId}.ts => {serviceName}.ts} (58%) rename src/api/teams/{teamId}/workloads/{{workloadId}.ts => {workloadName}.ts} (57%) rename src/api/teams/{teamId}/workloads/{{workloadId} => {workloadName}}/values.ts (61%) diff --git a/src/api/teams/{teamId}/backups/{backupId}.ts b/src/api/teams/{teamId}/backups/{backupName}.ts similarity index 58% rename from src/api/teams/{teamId}/backups/{backupId}.ts rename to src/api/teams/{teamId}/backups/{backupName}.ts index 27b1f72f4..235685233 100644 --- a/src/api/teams/{teamId}/backups/{backupId}.ts +++ b/src/api/teams/{teamId}/backups/{backupName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:backups') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, backupId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteBackup(${backupId})`) - await otomi.deleteBackup(decodeURIComponent(teamId), decodeURIComponent(backupId)) + async ({ otomi, params: { teamId, backupName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteBackup(${backupName})`) + await otomi.deleteBackup(decodeURIComponent(teamId), decodeURIComponent(backupName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, backupId } }: OpenApiRequestExt, res): void => { - debug(`getBackup(${backupId})`) - const data = otomi.getBackup(decodeURIComponent(teamId), decodeURIComponent(backupId)) + ({ otomi, params: { teamId, backupName } }: OpenApiRequestExt, res): void => { + debug(`getBackup(${backupName})`) + const data = otomi.getBackup(decodeURIComponent(teamId), decodeURIComponent(backupName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, backupId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editBackup(${backupId})`) - const data = await otomi.editBackup(decodeURIComponent(teamId), decodeURIComponent(backupId), { + async ({ otomi, params: { teamId, backupName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editBackup(${backupName})`) + const data = await otomi.editBackup(decodeURIComponent(teamId), decodeURIComponent(backupName), { ...body, } as Backup) res.json(data) diff --git a/src/api/teams/{teamId}/builds/{buildId}.ts b/src/api/teams/{teamId}/builds/{buildName}.ts similarity index 60% rename from src/api/teams/{teamId}/builds/{buildId}.ts rename to src/api/teams/{teamId}/builds/{buildName}.ts index aa45eed55..3eca03781 100644 --- a/src/api/teams/{teamId}/builds/{buildId}.ts +++ b/src/api/teams/{teamId}/builds/{buildName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:builds') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, buildId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteBuild(${buildId})`) - await otomi.deleteBuild(decodeURIComponent(teamId), decodeURIComponent(buildId)) + async ({ otomi, params: { teamId, buildName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteBuild(${buildName})`) + await otomi.deleteBuild(decodeURIComponent(teamId), decodeURIComponent(buildName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, buildId } }: OpenApiRequestExt, res): void => { - debug(`getBuild(${buildId})`) - const data = otomi.getBuild(decodeURIComponent(teamId), decodeURIComponent(buildId)) + ({ otomi, params: { teamId, buildName } }: OpenApiRequestExt, res): void => { + debug(`getBuild(${buildName})`) + const data = otomi.getBuild(decodeURIComponent(teamId), decodeURIComponent(buildName)) res.json(data) }, ] const put: Operation = [ - ({ otomi, params: { teamId, buildId }, body }: OpenApiRequestExt, res): void => { - debug(`editBuild(${buildId})`) - const data = otomi.editBuild(decodeURIComponent(teamId), decodeURIComponent(buildId), { + ({ otomi, params: { teamId, buildName }, body }: OpenApiRequestExt, res): void => { + debug(`editBuild(${buildName})`) + const data = otomi.editBuild(decodeURIComponent(teamId), decodeURIComponent(buildName), { ...body, } as Build) res.json(data) diff --git a/src/api/teams/{teamId}/coderepos/{codeRepoId}.ts b/src/api/teams/{teamId}/coderepos/{codeRepositoryName}.ts similarity index 55% rename from src/api/teams/{teamId}/coderepos/{codeRepoId}.ts rename to src/api/teams/{teamId}/coderepos/{codeRepositoryName}.ts index eec0d22fb..6299272f5 100644 --- a/src/api/teams/{teamId}/coderepos/{codeRepoId}.ts +++ b/src/api/teams/{teamId}/coderepos/{codeRepositoryName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:codeRepos') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, codeRepoId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteCodeRepo(${codeRepoId})`) - await otomi.deleteCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId)) + async ({ otomi, params: { teamId, codeRepositoryName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteCodeRepo(${codeRepositoryName})`) + await otomi.deleteCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepositoryName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, codeRepoId } }: OpenApiRequestExt, res): void => { - debug(`getCodeRepo(${codeRepoId})`) - const data = otomi.getCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId)) + ({ otomi, params: { teamId, codeRepositoryName } }: OpenApiRequestExt, res): void => { + debug(`getCodeRepo(${codeRepositoryName})`) + const data = otomi.getCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepositoryName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, codeRepoId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editCodeRepo(${codeRepoId})`) - const data = await otomi.editCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepoId), { + async ({ otomi, params: { teamId, codeRepositoryName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editCodeRepo(${codeRepositoryName})`) + const data = await otomi.editCodeRepo(decodeURIComponent(teamId), decodeURIComponent(codeRepositoryName), { ...body, } as CodeRepo) res.json(data) diff --git a/src/api/teams/{teamId}/netpols/{netpolId}.ts b/src/api/teams/{teamId}/netpols/{netpolName}.ts similarity index 58% rename from src/api/teams/{teamId}/netpols/{netpolId}.ts rename to src/api/teams/{teamId}/netpols/{netpolName}.ts index 9d445e4e4..fc79833c8 100644 --- a/src/api/teams/{teamId}/netpols/{netpolId}.ts +++ b/src/api/teams/{teamId}/netpols/{netpolName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:netpols') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, netpolId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteNetpol(${netpolId})`) - await otomi.deleteNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId)) + async ({ otomi, params: { teamId, netpolName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteNetpol(${netpolName})`) + await otomi.deleteNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, netpolId } }: OpenApiRequestExt, res): void => { - debug(`getNetpol(${netpolId})`) - const data = otomi.getNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId)) + ({ otomi, params: { teamId, netpolName } }: OpenApiRequestExt, res): void => { + debug(`getNetpol(${netpolName})`) + const data = otomi.getNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, netpolId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editNetpol(${netpolId})`) - const data = await otomi.editNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolId), { + async ({ otomi, params: { teamId, netpolName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editNetpol(${netpolName})`) + const data = await otomi.editNetpol(decodeURIComponent(teamId), decodeURIComponent(netpolName), { ...body, } as Netpol) res.json(data) diff --git a/src/api/teams/{teamId}/projects/{projectId}.ts b/src/api/teams/{teamId}/projects/{projectName}.ts similarity index 59% rename from src/api/teams/{teamId}/projects/{projectId}.ts rename to src/api/teams/{teamId}/projects/{projectName}.ts index 567d51097..e63509c57 100644 --- a/src/api/teams/{teamId}/projects/{projectId}.ts +++ b/src/api/teams/{teamId}/projects/{projectName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:projects') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, projectId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteProject(${projectId})`) - await otomi.deleteProject(decodeURIComponent(teamId), decodeURIComponent(projectId)) + async ({ otomi, params: { teamId, projectName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteProject(${projectName})`) + await otomi.deleteProject(decodeURIComponent(teamId), decodeURIComponent(projectName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, projectId } }: OpenApiRequestExt, res): void => { - debug(`getProject(${projectId})`) - const data = otomi.getProject(decodeURIComponent(teamId), decodeURIComponent(projectId)) + ({ otomi, params: { teamId, projectName } }: OpenApiRequestExt, res): void => { + debug(`getProject(${projectName})`) + const data = otomi.getProject(decodeURIComponent(teamId), decodeURIComponent(projectName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, projectId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editProject(${projectId})`) - const data = await otomi.editProject(decodeURIComponent(teamId), decodeURIComponent(projectId), { + async ({ otomi, params: { teamId, projectName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editProject(${projectName})`) + const data = await otomi.editProject(decodeURIComponent(teamId), decodeURIComponent(projectName), { ...body, teamId: decodeURIComponent(teamId), } as Project) diff --git a/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts b/src/api/teams/{teamId}/sealedsecrets/{sealedSecretName}.ts similarity index 57% rename from src/api/teams/{teamId}/sealedsecrets/{secretId}.ts rename to src/api/teams/{teamId}/sealedsecrets/{sealedSecretName}.ts index d8cbf050d..bbbd2bd6e 100644 --- a/src/api/teams/{teamId}/sealedsecrets/{secretId}.ts +++ b/src/api/teams/{teamId}/sealedsecrets/{sealedSecretName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:sealedsecrets') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, secretId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteSealedSecret(${secretId})`) - await otomi.deleteSealedSecret(decodeURIComponent(teamId), decodeURIComponent(secretId)) + async ({ otomi, params: { teamId, sealedSecretName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteSealedSecret(${sealedSecretName})`) + await otomi.deleteSealedSecret(decodeURIComponent(teamId), decodeURIComponent(sealedSecretName)) res.json({}) }, ] const get: Operation = [ - async ({ otomi, params: { teamId, secretId } }: OpenApiRequestExt, res): Promise => { - debug(`getSealedSecret(${secretId})`) - const data = await otomi.getSealedSecret(decodeURIComponent(teamId), decodeURIComponent(secretId)) + async ({ otomi, params: { teamId, sealedSecretName } }: OpenApiRequestExt, res): Promise => { + debug(`getSealedSecret(${sealedSecretName})`) + const data = await otomi.getSealedSecret(decodeURIComponent(teamId), decodeURIComponent(sealedSecretName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, secretId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editSealedSecret(${secretId})`) - const data = await otomi.editSealedSecret(decodeURIComponent(secretId), { + async ({ otomi, params: { teamId, sealedSecretName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editSealedSecret(${sealedSecretName})`) + const data = await otomi.editSealedSecret(decodeURIComponent(sealedSecretName), { ...body, teamId: decodeURIComponent(teamId), } as SealedSecret) diff --git a/src/api/teams/{teamId}/services/{serviceId}.ts b/src/api/teams/{teamId}/services/{serviceName}.ts similarity index 58% rename from src/api/teams/{teamId}/services/{serviceId}.ts rename to src/api/teams/{teamId}/services/{serviceName}.ts index b97bfb02d..86b61ffd3 100644 --- a/src/api/teams/{teamId}/services/{serviceId}.ts +++ b/src/api/teams/{teamId}/services/{serviceName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:services') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, serviceId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteService(${serviceId})`) - await otomi.deleteService(decodeURIComponent(teamId), decodeURIComponent(serviceId)) + async ({ otomi, params: { teamId, serviceName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteService(${serviceName})`) + await otomi.deleteService(decodeURIComponent(teamId), decodeURIComponent(serviceName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, serviceId } }: OpenApiRequestExt, res): void => { - debug(`getService(${serviceId})`) - const data = otomi.getService(decodeURIComponent(teamId), decodeURIComponent(serviceId)) + ({ otomi, params: { teamId, serviceName } }: OpenApiRequestExt, res): void => { + debug(`getService(${serviceName})`) + const data = otomi.getService(decodeURIComponent(teamId), decodeURIComponent(serviceName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, serviceId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editService(${serviceId})`) - const data = await otomi.editService(decodeURIComponent(teamId), decodeURIComponent(serviceId), { + async ({ otomi, params: { teamId, serviceName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editService(${serviceName})`) + const data = await otomi.editService(decodeURIComponent(teamId), decodeURIComponent(serviceName), { ...body, } as Service) res.json(data) diff --git a/src/api/teams/{teamId}/workloads/{workloadId}.ts b/src/api/teams/{teamId}/workloads/{workloadName}.ts similarity index 57% rename from src/api/teams/{teamId}/workloads/{workloadId}.ts rename to src/api/teams/{teamId}/workloads/{workloadName}.ts index 584d3ca85..f7e8c9b08 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}.ts +++ b/src/api/teams/{teamId}/workloads/{workloadName}.ts @@ -6,23 +6,23 @@ const debug = Debug('otomi:api:teams:workloads') export default function (): OperationHandlerArray { const del: Operation = [ - async ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): Promise => { - debug(`deleteWorkload(${workloadId})`) - await otomi.deleteWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId)) + async ({ otomi, params: { teamId, workloadName } }: OpenApiRequestExt, res): Promise => { + debug(`deleteWorkload(${workloadName})`) + await otomi.deleteWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadName)) res.json({}) }, ] const get: Operation = [ - ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): void => { - debug(`getWorkload(${workloadId})`) - const data = otomi.getWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId)) + ({ otomi, params: { teamId, workloadName } }: OpenApiRequestExt, res): void => { + debug(`getWorkload(${workloadName})`) + const data = otomi.getWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, workloadId }, body }: OpenApiRequestExt, res): Promise => { - debug(`editWorkload(${workloadId})`) - const data = await otomi.editWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadId), { + async ({ otomi, params: { teamId, workloadName }, body }: OpenApiRequestExt, res): Promise => { + debug(`editWorkload(${workloadName})`) + const data = await otomi.editWorkload(decodeURIComponent(teamId), decodeURIComponent(workloadName), { ...body, } as Workload) res.json(data) diff --git a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts b/src/api/teams/{teamId}/workloads/{workloadName}/values.ts similarity index 61% rename from src/api/teams/{teamId}/workloads/{workloadId}/values.ts rename to src/api/teams/{teamId}/workloads/{workloadName}/values.ts index aba455112..32541e97b 100644 --- a/src/api/teams/{teamId}/workloads/{workloadId}/values.ts +++ b/src/api/teams/{teamId}/workloads/{workloadName}/values.ts @@ -6,17 +6,17 @@ const debug = Debug('otomi:api:teams:workloadValues') export default function (): OperationHandlerArray { const get: Operation = [ - ({ otomi, params: { teamId, workloadId } }: OpenApiRequestExt, res): void => { - debug(`getWorkloadValues(${workloadId})`) - const data = otomi.getWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadId)) + ({ otomi, params: { teamId, workloadName } }: OpenApiRequestExt, res): void => { + debug(`getWorkloadValues(${workloadName})`) + const data = otomi.getWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadName)) res.json(data) }, ] const put: Operation = [ - async ({ otomi, params: { teamId, workloadId }, body }: OpenApiRequestExt, res): Promise => { - debug(`putWorkloadValues(${workloadId})`) - const data = await otomi.editWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadId), { + async ({ otomi, params: { teamId, workloadName }, body }: OpenApiRequestExt, res): Promise => { + debug(`putWorkloadValues(${workloadName})`) + const data = await otomi.editWorkloadValues(decodeURIComponent(teamId), decodeURIComponent(workloadName), { ...body, } as WorkloadValues) res.json(data) diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 72fa336a8..85488d8d2 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -244,7 +244,7 @@ paths: '400': <<: *BadRequest - '/teams/{teamId}/services/{serviceId}': + '/teams/{teamId}/services/{serviceName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/serviceParams' @@ -389,10 +389,10 @@ paths: schema: $ref: '#/components/schemas/SealedSecret' - '/teams/{teamId}/sealedsecrets/{secretId}': + '/teams/{teamId}/sealedsecrets/{sealedSecretName}': parameters: - $ref: '#/components/parameters/teamParams' - - $ref: '#/components/parameters/secretParams' + - $ref: '#/components/parameters/sealedSecretParams' get: operationId: getSealedSecret description: Get a sealed secret from a given team @@ -481,50 +481,6 @@ paths: schema: $ref: '#/components/schemas/Secret' - '/teams/{teamId}/secrets/{secretId}': - parameters: - - $ref: '#/components/parameters/teamParams' - - $ref: '#/components/parameters/secretParams' - get: - operationId: getSecret - description: Get a secret from a given team - x-aclSchema: Secret - responses: - <<: *DefaultGetResponses - '200': - description: Successfully obtained secret configuration - content: - application/json: - schema: - $ref: '#/components/schemas/Secret' - put: - operationId: editSecret - description: Edit a secret from a given team - x-aclSchema: Secret - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Secret' - description: Secret object that contains updated values - required: true - responses: - <<: *DefaultGetResponses - '200': - description: Successfully edited a team secret - content: - application/json: - schema: - $ref: '#/components/schemas/Secret' - delete: - operationId: deleteSecret - description: Delete a secret from a given team - x-aclSchema: Secret - responses: - <<: *DefaultGetResponses - '200': - description: Successfully deleted a team secret - '/netpols': get: operationId: getAllNetpols @@ -576,7 +532,7 @@ paths: schema: $ref: '#/components/schemas/Netpol' - '/teams/{teamId}/netpols/{netpolId}': + '/teams/{teamId}/netpols/{netpolName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/netpolParams' @@ -671,7 +627,7 @@ paths: schema: $ref: '#/components/schemas/Backup' - '/teams/{teamId}/backups/{backupId}': + '/teams/{teamId}/backups/{backupName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/backupParams' @@ -780,7 +736,7 @@ paths: schema: $ref: '#/components/schemas/Build' - '/teams/{teamId}/builds/{buildId}': + '/teams/{teamId}/builds/{buildName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/buildParams' @@ -1101,7 +1057,7 @@ paths: schema: $ref: '#/components/schemas/Project' - '/teams/{teamId}/projects/{projectId}': + '/teams/{teamId}/projects/{projectName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/projectParams' @@ -1211,7 +1167,7 @@ paths: schema: $ref: '#/components/schemas/CodeRepo' - '/teams/{teamId}/coderepos/{codeRepoId}': + '/teams/{teamId}/coderepos/{codeRepositoryName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/codeRepoParams' @@ -1326,7 +1282,7 @@ paths: schema: $ref: '#/components/schemas/Workload' - '/teams/{teamId}/workloads/{workloadId}': + '/teams/{teamId}/workloads/{workloadName}': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/workloadParams' @@ -1370,7 +1326,7 @@ paths: schema: $ref: '#/components/schemas/Workload' - '/teams/{teamId}/workloads/{workloadId}/values': + '/teams/{teamId}/workloads/{workloadName}/values': parameters: - $ref: '#/components/parameters/teamParams' - $ref: '#/components/parameters/workloadParams' @@ -1684,14 +1640,14 @@ servers: components: parameters: backupParams: - name: backupId + name: backupName in: path description: ID of the backup required: true schema: type: string buildParams: - name: buildId + name: buildName in: path description: ID of the build required: true @@ -1712,16 +1668,16 @@ components: schema: type: string projectParams: - name: projectId + name: projectName in: path description: ID of the project required: true schema: type: string codeRepoParams: - name: codeRepoId + name: codeRepositoryName in: path - description: ID of the code repo + description: Name of the code repository required: true schema: type: string @@ -1733,23 +1689,23 @@ components: schema: type: string netpolParams: - name: netpolId + name: netpolName in: path - description: ID of the network policy + description: Name of the network policy required: true schema: type: string serviceParams: - name: serviceId + name: serviceName in: path description: ID of the service required: true schema: type: string - secretParams: - name: secretId + sealedSecretParams: + name: sealedSecretName in: path - description: ID of the secret + description: Name of the sealed secret required: true schema: type: string @@ -1761,7 +1717,7 @@ components: schema: type: string workloadParams: - name: workloadId + name: workloadName in: path description: ID of the workload required: true diff --git a/src/openapi/codeRepo.yaml b/src/openapi/codeRepo.yaml index ad3574446..449ecb9d5 100644 --- a/src/openapi/codeRepo.yaml +++ b/src/openapi/codeRepo.yaml @@ -20,7 +20,7 @@ CodeRepo: type: string teamId: $ref: definitions.yaml#/idName - label: + name: $ref: 'definitions.yaml#/idName' gitService: type: string @@ -37,7 +37,7 @@ CodeRepo: secret: type: string required: - - label + - name - gitService - repositoryUrl type: object diff --git a/src/openapi/settingsinfo.yaml b/src/openapi/settingsinfo.yaml index 7261b2d9f..876a2aadc 100644 --- a/src/openapi/settingsinfo.yaml +++ b/src/openapi/settingsinfo.yaml @@ -33,6 +33,10 @@ SettingsInfo: hasExternalIDP: type: boolean default: false + smtp: + properties: + smarthost: + type: string ingressClassNames: description: Ingress class names that are used by the cluster. items: diff --git a/src/otomi-models.ts b/src/otomi-models.ts index 496deebcd..d864ca216 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -176,7 +176,7 @@ export interface TeamConfig { netpols: Netpol[] policies: Policies projects: Project[] - sealedSecrets: SealedSecret[] + sealedsecrets: SealedSecret[] services: Service[] settings: Team workloads: Workload[] diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index ff3a3bed8..86fd6dcc0 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -10,6 +10,7 @@ import { generate as generatePassword } from 'generate-password' import { cloneDeep, filter, isEmpty, map, mapValues, omit, pick, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' +import getRepo, { Git } from 'src/git' import { cleanAllSessions, cleanSession, DbMessage, getIo, getSessionStack } from 'src/middleware' import { App, @@ -38,7 +39,6 @@ import { Workload, WorkloadValues, } from 'src/otomi-models' -import getRepo, { Git } from 'src/git' import { arrayToObject, getValuesSchema, removeBlankAttributes } from 'src/utils' import { cleanEnv, @@ -70,6 +70,8 @@ import { k8sdelete, watchPodUntilRunning, } from './k8s_operations' +import { getFileMaps, loadValues } from './repo' +import { RepoService } from './services/RepoService' import { validateBackupFields } from './utils/backupUtils' import { getGiteaRepoUrls, @@ -82,8 +84,6 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' -import { getFileMaps, loadValues } from './repo' -import { RepoService } from './services/RepoService' interface ExcludedApp extends App { managed: boolean @@ -801,7 +801,6 @@ export default class OtomiStack { services = {} } return { - id: name, teamId, ...project, name: project.name, @@ -814,42 +813,49 @@ export default class OtomiStack { async editProject(teamId: string, name: string, data: Project): Promise { const { build, workload, workloadValues, service } = data - let b, w, wv, s - if (!build?.name && build?.mode) { - b = this.repoService.getTeamConfigService(teamId).createBuild({ ...build, teamId }) - } else if (build?.name) { - b = this.repoService.getTeamConfigService(teamId).updateBuild(build.name, build) + + if (build) { + try { + b = this.repoService.getTeamConfigService(teamId).createBuild({ ...build, teamId }) + } catch (error) { + if (error.code == 409) b = this.repoService.getTeamConfigService(teamId).updateBuild(build.name, build) + } } - if (workload && !workload?.name) { - w = this.repoService.getTeamConfigService(teamId).createWorkload(workload) - } else if (workload?.name) { - w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.name, workload) + if (workload) { + try { + w = this.repoService.getTeamConfigService(teamId).createWorkload(workload) + } catch (error) { + if (error.code === 409) + w = this.repoService.getTeamConfigService(teamId).updateWorkload(workload.name, workload) + } } - if (workloadValues && !workloadValues?.name) { - wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...workloadValues, name }) - } else if (workloadValues?.name) { - wv = this.repoService - .getTeamConfigService(teamId) - .updateWorkloadValues(workloadValues.name, { ...workloadValues, name }) + if (workload && workloadValues) { + try { + wv = this.repoService.getTeamConfigService(teamId).createWorkloadValues({ ...workloadValues, name }) + } catch (error) { + if (error.code === 409) + wv = this.repoService.getTeamConfigService(teamId).updateWorkloadValues(name, { ...workloadValues, name }) + } } - if (service && !service?.name) { - s = this.repoService.getTeamConfigService(teamId).createService({ ...service, teamId }) - } else if (service?.name) { - s = this.repoService.getTeamConfigService(teamId).updateService(service.name, service) + if (service) { + try { + s = this.repoService.getTeamConfigService(teamId).createService({ ...service, teamId }) + } catch (error) { + if (error.code === 409) s = this.repoService.getTeamConfigService(teamId).updateService(service.name, service) + } } const updatedData = { - id: name, name, teamId, - ...(b && { build: { id: b.id } }), - workload: { id: w.id }, - workloadValues: { id: wv.id }, - service: { id: s.id }, + ...(b && { build: { name: b.name } }), + workload: { name: w.name }, + workloadValues: { name: wv.name }, + service: { name: s.name }, } let project: Project @@ -914,22 +920,22 @@ export default class OtomiStack { } } - getCodeRepo(teamId: string, id: string): CodeRepo { - return this.repoService.getTeamConfigService(teamId).getCodeRepo(id) + getCodeRepo(teamId: string, name: string): CodeRepo { + return this.repoService.getTeamConfigService(teamId).getCodeRepo(name) } - async editCodeRepo(teamId: string, id: string, data: CodeRepo): Promise { + async editCodeRepo(teamId: string, name: string, data: CodeRepo): Promise { const body = { ...data } if (!body.private) unset(body, 'secret') // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(id, body) + const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, body) await this.saveTeamCodeRepo(teamId, codeRepo) await this.doDeployment(['codeRepos'], teamId) return codeRepo } - async deleteCodeRepo(teamId: string, id: string): Promise { - await this.deleteTeamCodeRepo(teamId, id) + async deleteCodeRepo(teamId: string, name: string): Promise { + await this.deleteTeamCodeRepo(teamId, name) await this.doDeployment(['codeRepos'], teamId) } @@ -1716,15 +1722,15 @@ export default class OtomiStack { } async saveTeamCodeRepo(teamId: string, codeRepo: CodeRepo): Promise { - debug(`Saving codeRepo ${codeRepo.label} for team ${teamId}`) + debug(`Saving codeRepo ${codeRepo.name} for team ${teamId}`) const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! await this.git.saveConfig(repo, fileMap) } - async deleteTeamCodeRepo(teamId: string, id: string): Promise { - const codeRepo = this.repoService.getTeamConfigService(teamId).getCodeRepo(id) - this.repoService.getTeamConfigService(teamId).deleteCodeRepo(id) + async deleteTeamCodeRepo(teamId: string, label: string): Promise { + const codeRepo = this.repoService.getTeamConfigService(teamId).getCodeRepo(label) + this.repoService.getTeamConfigService(teamId).deleteCodeRepo(label) const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! @@ -1760,9 +1766,31 @@ export default class OtomiStack { await this.git.deleteConfig(repo, fileMap) } + convertDbServiceToValues(svc: any): any { + const svcCloned = omit(svc, ['teamId', 'ingress', 'path']) + if (svc.ingress && svc.ingress.type !== 'cluster') { + const ing = svc.ingress + if (ing.useDefaultHost) svcCloned.ownHost = true + else svcCloned.domain = ing.subdomain ? `${ing.subdomain}.${ing.domain}` : ing.domain + if (ing.hasCert) svcCloned.hasCert = true + if (ing.certName) svcCloned.certName = ing.certName + if (ing.certArn) svcCloned.certArn = ing.certArn + if (ing.paths) svcCloned.paths = ing.paths + if (ing.forwardPath) svcCloned.forwardPath = true + if (ing.tlsPass) svcCloned.tlsPass = true + if (ing.ingressClassName) svcCloned.ingressClassName = ing.ingressClassName + if (ing.headers) svcCloned.headers = ing.headers + if (ing.useCname) svcCloned.useCname = ing.useCname + if (ing.cname) svcCloned.cname = ing.cname + svcCloned.type = svc.ingress.type + } else svcCloned.type = 'cluster' + return svcCloned + } + async saveTeamService(teamId: string, service: Service): Promise { debug(`Saving service: ${service.name} teamId: ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'services', service) + const newService = this.convertDbServiceToValues(service) + const repo = this.createTeamConfigInRepo(teamId, 'services', newService) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! await this.git.saveConfig(repo, fileMap) } diff --git a/src/repo.ts b/src/repo.ts index b9cadbabe..aee73a9c7 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -481,6 +481,9 @@ export async function loadFileToSpec( if (fileMap.kind === 'AplTeamWorkloadValues') { const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] ref.push({ ...data, name }) + } else if (fileMap.kind === 'AplTeamSecret') { + const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] + ref.push({ ...data?.spec, name }) } else { ref.push(data?.spec) } diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index bf055c9f6..56b2794ac 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -1,3 +1,6 @@ +import { find, has, merge, remove, set } from 'lodash' +import { v4 as uuidv4 } from 'uuid' +import { AlreadyExists, NotExistError } from '../error' import { App, Backup, @@ -14,9 +17,6 @@ import { Workload, WorkloadValues, } from '../otomi-models' -import { find, has, merge, remove, set } from 'lodash' -import { AlreadyExists, NotExistError } from '../error' -import { v4 as uuidv4 } from 'uuid' export class TeamConfigService { constructor(private teamConfig: TeamConfig) { @@ -24,7 +24,7 @@ export class TeamConfigService { this.teamConfig.workloads ??= [] this.teamConfig.workloadValues ??= [] this.teamConfig.services ??= [] - this.teamConfig.sealedSecrets ??= [] + this.teamConfig.sealedsecrets ??= [] this.teamConfig.backups ??= [] this.teamConfig.projects ??= [] this.teamConfig.netpols ??= [] @@ -78,18 +78,17 @@ export class TeamConfigService { public createCodeRepo(codeRepo: CodeRepo): CodeRepo { this.teamConfig.codeRepos ??= [] - const newCodeRepo = { ...codeRepo, id: codeRepo.id ?? uuidv4() } - if (find(this.teamConfig.codeRepos, { name: newCodeRepo.id })) { - throw new AlreadyExists(`CodeRepo[${newCodeRepo.id}] already exists.`) + if (find(this.teamConfig.codeRepos, { name: codeRepo.name })) { + throw new AlreadyExists(`CodeRepo[${codeRepo.name}] already exists.`) } - this.teamConfig.codeRepos.push(newCodeRepo) - return newCodeRepo + this.teamConfig.codeRepos.push(codeRepo) + return codeRepo } - public getCodeRepo(id: string): CodeRepo { - const codeRepo = find(this.teamConfig.codeRepos, { id }) + public getCodeRepo(name: string): CodeRepo { + const codeRepo = find(this.teamConfig.codeRepos, { name }) if (!codeRepo) { - throw new NotExistError(`CodeRepo[${id}] does not exist.`) + throw new NotExistError(`CodeRepo[${name}] does not exist.`) } return codeRepo } @@ -102,14 +101,14 @@ export class TeamConfigService { })) } - public updateCodeRepo(id: string, updates: Partial): CodeRepo { - const codeRepo = find(this.teamConfig.codeRepos, { id }) - if (!codeRepo) throw new NotExistError(`CodeRepo[${id}] does not exist.`) + public updateCodeRepo(name: string, updates: Partial): CodeRepo { + const codeRepo = find(this.teamConfig.codeRepos, { name }) + if (!codeRepo) throw new NotExistError(`CodeRepo[${name}] does not exist.`) return merge(codeRepo, updates) } - public deleteCodeRepo(id: string): void { - remove(this.teamConfig.codeRepos, { id }) + public deleteCodeRepo(name: string): void { + remove(this.teamConfig.codeRepos, { name }) } // ===================================== @@ -233,17 +232,17 @@ export class TeamConfigService { // ===================================== public createSealedSecret(secret: SealedSecret): SealedSecret { - this.teamConfig.sealedSecrets ??= [] + this.teamConfig.sealedsecrets ??= [] const newSecret = { ...secret, id: secret.id ?? uuidv4() } - if (find(this.teamConfig.sealedSecrets, { name: newSecret.name })) { + if (find(this.teamConfig.sealedsecrets, { name: newSecret.name })) { throw new AlreadyExists(`SealedSecret[${newSecret.name}] already exists.`) } - this.teamConfig.sealedSecrets.push(newSecret) + this.teamConfig.sealedsecrets.push(newSecret) return newSecret } public getSealedSecret(name: string): SealedSecret { - const sealedSecrets = find(this.teamConfig.sealedSecrets, { name }) + const sealedSecrets = find(this.teamConfig.sealedsecrets, { name }) if (!sealedSecrets) { throw new NotExistError(`SealedSecret[${name}] does not exist.`) } @@ -252,20 +251,20 @@ export class TeamConfigService { public getSealedSecrets(): SealedSecret[] { const teamId = this.teamConfig.settings?.id - return (this.teamConfig.sealedSecrets ?? []).map((sealedSecret) => ({ + return (this.teamConfig.sealedsecrets ?? []).map((sealedSecret) => ({ ...sealedSecret, teamId, })) } public updateSealedSecret(name: string, updates: Partial): SealedSecret { - const secret = find(this.teamConfig.sealedSecrets, { name }) + const secret = find(this.teamConfig.sealedsecrets, { name }) if (!secret) throw new NotExistError(`SealedSecret[${name}] does not exist.`) return merge(secret, updates) } public deleteSealedSecret(name: string): void { - remove(this.teamConfig.sealedSecrets, { name }) + remove(this.teamConfig.sealedsecrets, { name }) } // ===================================== From bf461e4a1eb175fd0238abdd40cbb064d8a95ec0 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 28 Feb 2025 09:18:29 +0100 Subject: [PATCH 26/49] fix: creation of service --- package-lock.json | 4652 +++++++++++++++++++++++++++++++++++--- src/openapi/api.yaml | 2 +- src/openapi/service.yaml | 1 + src/otomi-stack.ts | 59 +- 4 files changed, 4372 insertions(+), 342 deletions(-) diff --git a/package-lock.json b/package-lock.json index e56f3f3d5..5b3c3a465 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25172,6 +25172,13 @@ "@ucast/mongo2js": "^1.3.0" } }, + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true + }, "@commitlint/cli": { "version": "16.3.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-16.3.0.tgz", @@ -26479,6 +26486,115 @@ "fastq": "^1.6.0" } }, + "@octokit/auth-token": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.2.tgz", + "integrity": "sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==", + "dev": true + }, + "@octokit/core": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.4.tgz", + "integrity": "sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==", + "dev": true, + "requires": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.1.2", + "@octokit/request": "^9.2.1", + "@octokit/request-error": "^6.1.7", + "@octokit/types": "^13.6.2", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + } + }, + "@octokit/endpoint": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.3.tgz", + "integrity": "sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==", + "dev": true, + "requires": { + "@octokit/types": "^13.6.2", + "universal-user-agent": "^7.0.2" + } + }, + "@octokit/graphql": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.2.1.tgz", + "integrity": "sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==", + "dev": true, + "requires": { + "@octokit/request": "^9.2.2", + "@octokit/types": "^13.8.0", + "universal-user-agent": "^7.0.0" + } + }, + "@octokit/openapi-types": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-23.0.1.tgz", + "integrity": "sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==", + "dev": true + }, + "@octokit/plugin-paginate-rest": { + "version": "11.4.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.3.tgz", + "integrity": "sha512-tBXaAbXkqVJlRoA/zQVe9mUdb8rScmivqtpv3ovsC5xhje/a+NOCivs7eUhWBwCApJVsR4G5HMeaLbq7PxqZGA==", + "dev": true, + "requires": { + "@octokit/types": "^13.7.0" + } + }, + "@octokit/plugin-retry": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.4.tgz", + "integrity": "sha512-7AIP4p9TttKN7ctygG4BtR7rrB0anZqoU9ThXFk8nETqIfvgPUANTSYHqWYknK7W3isw59LpZeLI8pcEwiJdRg==", + "dev": true, + "requires": { + "@octokit/request-error": "^6.1.7", + "@octokit/types": "^13.6.2", + "bottleneck": "^2.15.3" + } + }, + "@octokit/plugin-throttling": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.4.0.tgz", + "integrity": "sha512-IOlXxXhZA4Z3m0EEYtrrACkuHiArHLZ3CvqWwOez/pURNqRuwfoFlTPbN5Muf28pzFuztxPyiUiNwz8KctdZaQ==", + "dev": true, + "requires": { + "@octokit/types": "^13.7.0", + "bottleneck": "^2.15.3" + } + }, + "@octokit/request": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.2.2.tgz", + "integrity": "sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==", + "dev": true, + "requires": { + "@octokit/endpoint": "^10.1.3", + "@octokit/request-error": "^6.1.7", + "@octokit/types": "^13.6.2", + "fast-content-type-parse": "^2.0.0", + "universal-user-agent": "^7.0.2" + } + }, + "@octokit/request-error": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.7.tgz", + "integrity": "sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==", + "dev": true, + "requires": { + "@octokit/types": "^13.6.2" + } + }, + "@octokit/types": { + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.8.0.tgz", + "integrity": "sha512-x7DjTIbEpEWXK99DMd01QfWy0hd5h4EN+Q7shkdKds3otGQP+oWE/y0A76i1OvH9fygo4ddvNf7ZvF0t78P98A==", + "dev": true, + "requires": { + "@octokit/openapi-types": "^23.0.1" + } + }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -26507,6 +26623,40 @@ } } }, + "@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true + }, + "@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "requires": { + "graceful-fs": "4.2.10" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + } + } + }, + "@pnpm/npm-conf": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", + "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", + "dev": true, + "requires": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + } + }, "@redocly/ajv": { "version": "8.6.4", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", @@ -26606,253 +26756,897 @@ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==" }, - "@sentry/core": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.17.7.tgz", - "integrity": "sha512-SRhLkD05lQb4eCt1ed9Dz72DKbRDlM8PJix8eC2oJLtwyFTS0IlJNkIYRrbsSKkJUm0VsKcDkzIHvUAgBBQICw==", - "requires": { - "@sentry/hub": "6.17.7", - "@sentry/minimal": "6.17.7", - "@sentry/types": "6.17.7", - "@sentry/utils": "6.17.7", - "tslib": "^1.9.3" - } + "@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true }, - "@sentry/hub": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.17.7.tgz", - "integrity": "sha512-siGzcg+quGOdjRaBGAz6T3ycwHUsGgvalptSJdf5Q783FVFhU+haPul++zGOYURXOgx0RjYGWqagwO8+jljl3Q==", + "@semantic-release/changelog": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", + "integrity": "sha512-dZuR5qByyfe3Y03TpmCvAxCyTnp7r5XwtHRf/8vD9EAn4ZWbavUX8adMtXYzE86EVh0gyLA7lm5yW4IV30XUag==", + "dev": true, "requires": { - "@sentry/types": "6.17.7", - "@sentry/utils": "6.17.7", - "tslib": "^1.9.3" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "fs-extra": "^11.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + } } }, - "@sentry/minimal": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.17.7.tgz", - "integrity": "sha512-+/FGem1uXsXikX9wHPw44nevO7YTVjkkiPjyLsvnWMjv64r4Au5s+NQSFHDaytRm9IlU//+OasCAS5VAwHcYRg==", + "@semantic-release/commit-analyzer": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.1.tgz", + "integrity": "sha512-wdnBPHKkr9HhNhXOhZD5a2LNl91+hs8CC2vsAVYxtZH3y0dV3wKn+uZSN61rdJQZ8EGxzWB3inWocBHV9+u/CQ==", + "dev": true, "requires": { - "@sentry/hub": "6.17.7", - "@sentry/types": "6.17.7", - "tslib": "^1.9.3" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "dependencies": { + "conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "requires": { + "compare-func": "^2.0.0" + } + }, + "conventional-changelog-writer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", + "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "dev": true, + "requires": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + } + }, + "conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true + }, + "conventional-commits-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", + "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "dev": true, + "requires": { + "meow": "^13.0.0" + } + }, + "meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + } } }, - "@sentry/node": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.17.7.tgz", - "integrity": "sha512-YlBhEv8YYUimHLpwzUP5lXkagbGxGAfjvsbahhvqf7rRl8Fu5XbatAcSDcx7YE4R0Iox94IfZy95kF2NL4Idow==", - "requires": { - "@sentry/core": "6.17.7", - "@sentry/hub": "6.17.7", - "@sentry/tracing": "6.17.7", - "@sentry/types": "6.17.7", - "@sentry/utils": "6.17.7", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - } + "@semantic-release/error": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-3.0.0.tgz", + "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", + "dev": true }, - "@sentry/tracing": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.17.7.tgz", - "integrity": "sha512-QzIDHOjjdi/0LTdrK2LTC27YEOODI473KD8KmMJ+r9PmjDeIjNzz4hJlPwQSnXR3Mu/8foxGJGXsAt3LNmKzlQ==", + "@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, "requires": { - "@sentry/hub": "6.17.7", - "@sentry/minimal": "6.17.7", - "@sentry/types": "6.17.7", - "@sentry/utils": "6.17.7", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.17.7.tgz", - "integrity": "sha512-iBlJDhrSowZKeqvutY0tCkUjrWqkLFsHrbaQ553r1Nx+/4mxHjzVYtEVGMjZAxQUEbkm0TbnQIkkT7ltglNJ9A==" - }, - "@sentry/utils": { - "version": "6.17.7", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.17.7.tgz", - "integrity": "sha512-HEEEeKlZtwfQvH0waSKv5FKRFjHkVgkkEiAigXoYGQAlaUIuwRTvZGFnsmBoKMIrA4pARkA00FwwdtMU7ziC8A==", - "requires": { - "@sentry/types": "6.17.7", - "tslib": "^1.9.3" + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "dependencies": { + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } } }, - "@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true - }, - "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "@semantic-release/github": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-11.0.1.tgz", + "integrity": "sha512-Z9cr0LgU/zgucbT9cksH0/pX9zmVda9hkDPcgIE0uvjMQ8w/mElDivGjx1w1pEQ+MuQJ5CBq3VCF16S6G4VH3A==", "dev": true, "requires": { - "type-detect": "4.0.8" - } - }, - "@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" - }, - "@swagger-api/apidom-ast": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.3.tgz", - "integrity": "sha512-JOXGfadL3ucJH+MY9BDT7dJOwFy0jX3XaAY/CWR92EnliEYfaEzZvH08FGnyqyYHcfT8T0DLKna5CWUHaskZuw==", - "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "unraw": "^3.0.0" - } - }, - "@swagger-api/apidom-core": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.3.tgz", - "integrity": "sha512-oRcv3PgwSAvfxvai0afGt/rC2Kk9Zs2ArLPZ6FnVCv/GSnMsuvIQJc5UH29P9eGFcLJIZpQtEHnU6W+u8u0zAA==", - "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "minim": "~0.23.8", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "short-unique-id": "^5.0.2", - "ts-mixer": "^6.0.3" - } - }, - "@swagger-api/apidom-error": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.3.tgz", - "integrity": "sha512-cW1tzehphuxA0uM+1m4/0G1d/WjDQyF+RL9D9t1mfhuVxr8AorgYUgY+bjg0pkLfiSTwjrDiuTbYM+jZwrHx8w==", - "requires": { - "@babel/runtime-corejs3": "^7.20.7" - } - }, - "@swagger-api/apidom-json-pointer": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.3.tgz", - "integrity": "sha512-r6Gvbj2XDcK1wIULoclHcGYPAVXeUkj5ECRslB/Zle/fOU0Jb8s4mmFARyQE/DT+fQggXn8nUJBda3NWPK4GcA==", - "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0" - } - }, - "@swagger-api/apidom-ns-api-design-systems": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.3.tgz", - "integrity": "sha512-x+NiLR0xZ0VB8AMJr7ii+6A27AP2CGjLyPQr6JutnifXG+vpkjbgXCPyz2qlmrvuLIkBJIE2lBuyX3+qQXmgCw==", - "optional": true, - "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "dependencies": { + "@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true + }, + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "requires": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + } + }, + "clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "requires": { + "escape-string-regexp": "5.0.0" + } + }, + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, + "globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + } + }, + "https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "requires": { + "agent-base": "^7.1.2", + "debug": "4" + } + }, + "ignore": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true + }, + "mime": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-4.0.6.tgz", + "integrity": "sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==", + "dev": true + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true + }, + "slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true + } } }, - "@swagger-api/apidom-ns-asyncapi-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.3.tgz", - "integrity": "sha512-9E4/kTf/OzV3vgRjZOB+6TRqQX2ljirD+UBQ8QPSJKBUTtq8+F7U9a8Z9AGYrKCQUMgbge5JMYCqHmOmrJKVUA==", - "optional": true, + "@semantic-release/npm": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", + "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "dev": true, "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" - } - }, - "@swagger-api/apidom-ns-json-schema-draft-4": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.3.tgz", - "integrity": "sha512-Sc/ywYCHFIMwhZX0Yo+OTmHUvszv3JE3xsvpd18nu7rH+jNyA10oUdTMgnRsTNMnL7siVO+32OKQkdLOSKsEHA==", - "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-ast": "^1.0.0-beta.3", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.4" + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.5.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "dependencies": { + "@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true + }, + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true + }, + "aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "requires": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + } + }, + "clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "requires": { + "escape-string-regexp": "5.0.0" + } + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, + "execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + } + }, + "figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "requires": { + "is-unicode-supported": "^2.0.0" + } + }, + "fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + }, + "hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "human-signals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "dev": true + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true + }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "requires": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "requires": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + } + }, + "parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + }, + "dependencies": { + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + } + } + }, + "registry-auth-token": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", + "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", + "dev": true, + "requires": { + "@pnpm/npm-conf": "^2.1.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true + }, + "type-fest": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz", + "integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==", + "dev": true + } } }, - "@swagger-api/apidom-ns-json-schema-draft-6": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.3.tgz", - "integrity": "sha512-UuGfaJfWzsTCTEyxyKtM86SNdS4EsWB/+j8JWw88h7nFK59YNDmnuXk9PpFyuccpIAHnDq7UJypD3lRvNkJdhQ==", - "optional": true, + "@semantic-release/release-notes-generator": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.3.tgz", + "integrity": "sha512-XxAZRPWGwO5JwJtS83bRdoIhCiYIx8Vhr+u231pQAsdFIAbm19rSVJLdnBN+Avvk7CKvNQE/nJ4y7uqKH6WTiw==", + "dev": true, "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.4" + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^2.0.0", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" + }, + "dependencies": { + "conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "requires": { + "compare-func": "^2.0.0" + } + }, + "conventional-changelog-writer": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz", + "integrity": "sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==", + "dev": true, + "requires": { + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + } + }, + "conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true + }, + "conventional-commits-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz", + "integrity": "sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==", + "dev": true, + "requires": { + "meow": "^13.0.0" + } + }, + "get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true + }, + "meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true + }, + "semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true + } } }, - "@swagger-api/apidom-ns-json-schema-draft-7": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.3.tgz", - "integrity": "sha512-7Snaf8/qZ3Q9xnjEXo2cJ8L4pvDbHA+k/j7rqbY4o3h5EeMy93ClVUwoeJ2y/JWax/V1DWTyYMhq+9dXlcIUYQ==", - "optional": true, + "@sentry/core": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.17.7.tgz", + "integrity": "sha512-SRhLkD05lQb4eCt1ed9Dz72DKbRDlM8PJix8eC2oJLtwyFTS0IlJNkIYRrbsSKkJUm0VsKcDkzIHvUAgBBQICw==", "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.4" + "@sentry/hub": "6.17.7", + "@sentry/minimal": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" } }, - "@swagger-api/apidom-ns-openapi-2": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.3.tgz", - "integrity": "sha512-eBNUkQdIDE2fWUXdIeRpN9OMxwfxU2WJFMRHst204Doanh8iJVp3Mz/+z9agHJ6Pkqth2XTXA0EDd1QiI37t+g==", - "optional": true, + "@sentry/hub": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.17.7.tgz", + "integrity": "sha512-siGzcg+quGOdjRaBGAz6T3ycwHUsGgvalptSJdf5Q783FVFhU+haPul++zGOYURXOgx0RjYGWqagwO8+jljl3Q==", "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", - "ramda": "~0.30.0", - "ramda-adjunct": "^5.0.0", - "ts-mixer": "^6.0.3" + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" } }, - "@swagger-api/apidom-ns-openapi-3-0": { - "version": "1.0.0-beta.3", - "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.3.tgz", - "integrity": "sha512-wKMdk5nplkT2PA1sRFZ2WOLmb7xi9++T6UnCeivmV+sy5NtUPpwkJLUWWIlZdZLyiGKmhZQ1gVvhsbyWRoAVPw==", + "@sentry/minimal": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.17.7.tgz", + "integrity": "sha512-+/FGem1uXsXikX9wHPw44nevO7YTVjkkiPjyLsvnWMjv64r4Au5s+NQSFHDaytRm9IlU//+OasCAS5VAwHcYRg==", "requires": { - "@babel/runtime-corejs3": "^7.20.7", - "@swagger-api/apidom-core": "^1.0.0-beta.3", - "@swagger-api/apidom-error": "^1.0.0-beta.3", - "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", - "@types/ramda": "~0.30.0", + "@sentry/hub": "6.17.7", + "@sentry/types": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/node": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.17.7.tgz", + "integrity": "sha512-YlBhEv8YYUimHLpwzUP5lXkagbGxGAfjvsbahhvqf7rRl8Fu5XbatAcSDcx7YE4R0Iox94IfZy95kF2NL4Idow==", + "requires": { + "@sentry/core": "6.17.7", + "@sentry/hub": "6.17.7", + "@sentry/tracing": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + } + }, + "@sentry/tracing": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.17.7.tgz", + "integrity": "sha512-QzIDHOjjdi/0LTdrK2LTC27YEOODI473KD8KmMJ+r9PmjDeIjNzz4hJlPwQSnXR3Mu/8foxGJGXsAt3LNmKzlQ==", + "requires": { + "@sentry/hub": "6.17.7", + "@sentry/minimal": "6.17.7", + "@sentry/types": "6.17.7", + "@sentry/utils": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sentry/types": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.17.7.tgz", + "integrity": "sha512-iBlJDhrSowZKeqvutY0tCkUjrWqkLFsHrbaQ553r1Nx+/4mxHjzVYtEVGMjZAxQUEbkm0TbnQIkkT7ltglNJ9A==" + }, + "@sentry/utils": { + "version": "6.17.7", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.17.7.tgz", + "integrity": "sha512-HEEEeKlZtwfQvH0waSKv5FKRFjHkVgkkEiAigXoYGQAlaUIuwRTvZGFnsmBoKMIrA4pARkA00FwwdtMU7ziC8A==", + "requires": { + "@sentry/types": "6.17.7", + "tslib": "^1.9.3" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true + }, + "@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" + }, + "@swagger-api/apidom-ast": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.0-beta.3.tgz", + "integrity": "sha512-JOXGfadL3ucJH+MY9BDT7dJOwFy0jX3XaAY/CWR92EnliEYfaEzZvH08FGnyqyYHcfT8T0DLKna5CWUHaskZuw==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "@swagger-api/apidom-core": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.0-beta.3.tgz", + "integrity": "sha512-oRcv3PgwSAvfxvai0afGt/rC2Kk9Zs2ArLPZ6FnVCv/GSnMsuvIQJc5UH29P9eGFcLJIZpQtEHnU6W+u8u0zAA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.0.2", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-error": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.0-beta.3.tgz", + "integrity": "sha512-cW1tzehphuxA0uM+1m4/0G1d/WjDQyF+RL9D9t1mfhuVxr8AorgYUgY+bjg0pkLfiSTwjrDiuTbYM+jZwrHx8w==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "@swagger-api/apidom-json-pointer": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.0-beta.3.tgz", + "integrity": "sha512-r6Gvbj2XDcK1wIULoclHcGYPAVXeUkj5ECRslB/Zle/fOU0Jb8s4mmFARyQE/DT+fQggXn8nUJBda3NWPK4GcA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.0-beta.3.tgz", + "integrity": "sha512-x+NiLR0xZ0VB8AMJr7ii+6A27AP2CGjLyPQr6JutnifXG+vpkjbgXCPyz2qlmrvuLIkBJIE2lBuyX3+qQXmgCw==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.0-beta.3.tgz", + "integrity": "sha512-9E4/kTf/OzV3vgRjZOB+6TRqQX2ljirD+UBQ8QPSJKBUTtq8+F7U9a8Z9AGYrKCQUMgbge5JMYCqHmOmrJKVUA==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.0-beta.3.tgz", + "integrity": "sha512-Sc/ywYCHFIMwhZX0Yo+OTmHUvszv3JE3xsvpd18nu7rH+jNyA10oUdTMgnRsTNMnL7siVO+32OKQkdLOSKsEHA==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-ast": "^1.0.0-beta.3", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.0-beta.3.tgz", + "integrity": "sha512-UuGfaJfWzsTCTEyxyKtM86SNdS4EsWB/+j8JWw88h7nFK59YNDmnuXk9PpFyuccpIAHnDq7UJypD3lRvNkJdhQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.0-beta.3.tgz", + "integrity": "sha512-7Snaf8/qZ3Q9xnjEXo2cJ8L4pvDbHA+k/j7rqbY4o3h5EeMy93ClVUwoeJ2y/JWax/V1DWTyYMhq+9dXlcIUYQ==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.0-beta.3.tgz", + "integrity": "sha512-eBNUkQdIDE2fWUXdIeRpN9OMxwfxU2WJFMRHst204Doanh8iJVp3Mz/+z9agHJ6Pkqth2XTXA0EDd1QiI37t+g==", + "optional": true, + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.0-beta.3", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.0-beta.3.tgz", + "integrity": "sha512-wKMdk5nplkT2PA1sRFZ2WOLmb7xi9++T6UnCeivmV+sy5NtUPpwkJLUWWIlZdZLyiGKmhZQ1gVvhsbyWRoAVPw==", + "requires": { + "@babel/runtime-corejs3": "^7.20.7", + "@swagger-api/apidom-core": "^1.0.0-beta.3", + "@swagger-api/apidom-error": "^1.0.0-beta.3", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.0-beta.3", + "@types/ramda": "~0.30.0", "ramda": "~0.30.0", "ramda-adjunct": "^5.0.0", "ts-mixer": "^6.0.3" @@ -27456,9 +28250,9 @@ "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==" }, "@types/normalize-package-data": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", - "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, "@types/parse-json": { @@ -27906,6 +28700,12 @@ "color-convert": "^2.0.1" } }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -27935,6 +28735,12 @@ "sprintf-js": "~1.0.2" } }, + "argv-formatter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/argv-formatter/-/argv-formatter-1.0.0.tgz", + "integrity": "sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==", + "dev": true + }, "aria-query": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", @@ -28321,6 +29127,12 @@ "tweetnacl": "^0.14.3" } }, + "before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -28407,6 +29219,12 @@ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.4.tgz", "integrity": "sha512-3hx0kwU3uzG6ReQ3pnaFQPSktpBw6RHN3/ivDKEuU8g1XSfafowyvDnadjv1xp8IZqhtSukxlwv9bF6FhX8m0w==" }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, "boxen": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", @@ -28860,12 +29678,53 @@ "restore-cursor": "^3.1.0" } }, - "cli-spinners": { - "version": "2.9.2", + "cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "dependencies": { + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + } + } + }, + "cli-spinners": { + "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true }, + "cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dev": true, + "requires": { + "@colors/colors": "1.5.0", + "string-width": "^4.2.0" + } + }, "cli-truncate": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", @@ -29081,6 +29940,16 @@ } } }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, "configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -29389,6 +30258,12 @@ "q": "^1.5.1" } }, + "convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true + }, "convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -30075,6 +30950,47 @@ } } }, + "duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -30143,6 +31059,12 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, + "emojilib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", + "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", + "dev": true + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -30226,6 +31148,101 @@ "ansi-colors": "^4.1.1" } }, + "env-ci": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/env-ci/-/env-ci-11.1.0.tgz", + "integrity": "sha512-Z8dnwSDbV1XYM9SBF2J0GcNVvmfmfh3a49qddGIROhBoVro6MZVTji15z/sJbQ2ko2ei8n988EU1wzoLU/tF+g==", + "dev": true, + "requires": { + "execa": "^8.0.0", + "java-properties": "^1.0.2" + }, + "dependencies": { + "execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + } + }, + "get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true + }, + "human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true + }, + "npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "requires": { + "path-key": "^4.0.0" + } + }, + "onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "requires": { + "mimic-fn": "^4.0.0" + } + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true + } + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true + }, "envalid": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/envalid/-/envalid-7.3.1.tgz", @@ -30241,6 +31258,12 @@ } } }, + "environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -31175,6 +32198,12 @@ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, + "fast-content-type-parse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-2.0.1.tgz", + "integrity": "sha512-nGqtvLrj5w0naR6tDPfB4cUmYCqouzyQiz6C5y/LtcDllJdrcc6WaWW6iXyIIOErTa/XRybj28aasdn4LkVk6Q==", + "dev": true + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -31193,26 +32222,26 @@ "dev": true }, "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "dependencies": { "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" + "braces": "^3.0.3", + "picomatch": "^2.3.1" } } } @@ -31398,6 +32427,22 @@ "path-exists": "^4.0.0" } }, + "find-up-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.0.tgz", + "integrity": "sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==", + "dev": true + }, + "find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, + "requires": { + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" + } + }, "findup-sync": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", @@ -31517,6 +32562,48 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -31593,6 +32680,12 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true + }, "generate-password": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/generate-password/-/generate-password-1.7.1.tgz", @@ -31776,23 +32869,88 @@ "integrity": "sha512-cSRL8IIOXU7UFLdbziCYqg8f8InwLwqHezkiRHNSph7oZqGv0togId1kMTfKil6gzK0VaSXeVBb4oDl0fQCHiw==", "dev": true }, - "git-raw-commits": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", - "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", - "dev": true, - "requires": { - "dargs": "^7.0.0", - "lodash": "^4.17.15", - "meow": "^8.0.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - } - }, - "git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", + "git-log-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/git-log-parser/-/git-log-parser-1.2.1.tgz", + "integrity": "sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==", + "dev": true, + "requires": { + "argv-formatter": "~1.0.0", + "spawn-error-forwarder": "~1.0.0", + "split2": "~1.0.0", + "stream-combiner2": "~1.1.1", + "through2": "~2.0.0", + "traverse": "0.6.8" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "split2": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-1.0.0.tgz", + "integrity": "sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==", + "dev": true, + "requires": { + "through2": "~2.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "git-raw-commits": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-2.0.11.tgz", + "integrity": "sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==", + "dev": true, + "requires": { + "dargs": "^7.0.0", + "lodash": "^4.17.15", + "meow": "^8.0.0", + "split2": "^3.0.0", + "through2": "^4.0.0" + } + }, + "git-remote-origin-url": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", + "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", "dev": true, "requires": { "gitconfiglocal": "^1.0.0", @@ -32131,6 +33289,12 @@ } } }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true + }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -32140,6 +33304,12 @@ "parse-passwd": "^1.0.0" } }, + "hook-std": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", + "integrity": "sha512-jHRQzjSDzMtFy34AGj1DN+vq54WVuhSvKgrHf0OMiFQTwDD4L/qqofVEWjLOBMTn5+lCD3fPg32W9yOfnEJTTw==", + "dev": true + }, "hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -32173,6 +33343,39 @@ "toidentifier": "1.0.1" } }, + "http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "dependencies": { + "agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "dev": true + }, + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "http-signature": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", @@ -32274,6 +33477,33 @@ } } }, + "import-from-esm": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-2.0.0.tgz", + "integrity": "sha512-YVt14UZCgsX1vZQ3gKjkWVdBdHQ6eu3MPU1TBgL1H5orXe2+jWD006WCPPtOuwlQm10NuzOW5WawiF1Q9veW8g==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "import-meta-resolve": "^4.0.0" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -32290,6 +33520,12 @@ "resolve-cwd": "^3.0.0" } }, + "import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -32302,6 +33538,12 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, + "index-to-position": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-0.1.2.tgz", + "integrity": "sha512-MWDKS3AS1bGCHLBA2VLImJz42f7bJh8wQsTGCzI3j519/CASStoDONUBVz2I/VID0MpiX3SGSnbOD2xUalbE5g==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -32361,6 +33603,16 @@ "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, + "into-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-7.0.0.tgz", + "integrity": "sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==", + "dev": true, + "requires": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -32765,6 +34017,19 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", + "dev": true, + "requires": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + } + }, "istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -32885,6 +34150,12 @@ } } }, + "java-properties": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/java-properties/-/java-properties-1.0.2.tgz", + "integrity": "sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==", + "dev": true + }, "jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -33981,6 +35252,18 @@ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" }, + "lodash.capitalize": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz", + "integrity": "sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==", + "dev": true + }, + "lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "dev": true + }, "lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", @@ -34003,6 +35286,12 @@ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, + "lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "dev": true + }, "lodash.map": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", @@ -34025,6 +35314,12 @@ "resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz", "integrity": "sha1-EjBkIvYzJK7YSD0/ODMrX2cFR6A=" }, + "lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -34176,6 +35471,50 @@ "object-visit": "^1.0.0" } }, + "marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true + }, + "marked-terminal": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.3.0.tgz", + "integrity": "sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==", + "dev": true, + "requires": { + "ansi-escapes": "^7.0.0", + "ansi-regex": "^6.1.0", + "chalk": "^5.4.1", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.2.0", + "supports-hyperlinks": "^3.1.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "requires": { + "environment": "^1.0.0" + } + }, + "ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true + }, + "chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -34533,6 +35872,17 @@ "integrity": "sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanoclone": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", @@ -34585,6 +35935,12 @@ "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==" }, + "nerf-dart": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nerf-dart/-/nerf-dart-1.0.0.tgz", + "integrity": "sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==", + "dev": true + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -34634,6 +35990,18 @@ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==" }, + "node-emoji": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", + "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", + "dev": true, + "requires": { + "@sindresorhus/is": "^4.6.0", + "char-regex": "^1.0.2", + "emojilib": "^2.4.0", + "skin-tone": "^2.0.0" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -34694,63 +36062,1915 @@ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "^2.1.1" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "noms": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", + "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" + } + }, + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "dev": true, + "requires": { + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", + "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", + "dev": true + }, + "npm": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.2.tgz", + "integrity": "sha512-iriPEPIkoMYUy3F6f3wwSZAU93E0Eg6cHwIR6jzzOXWSy+SD/rOODEs74cVONHKSx2obXtuUoyidVEhISrisgQ==", + "dev": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.0", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.1.0", + "@npmcli/promise-spawn": "^8.0.2", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "@sigstore/tuf": "^3.0.0", + "abbrev": "^3.0.0", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.3.0", + "ci-info": "^4.1.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.4.5", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.0.2", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.0", + "libnpmexec": "^9.0.0", + "libnpmfund": "^6.0.0", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.0", + "libnpmpublish": "^10.0.1", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.0.0", + "nopt": "^8.0.0", + "normalize-package-data": "^7.0.0", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.1", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^4.0.0", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.0.0", + "semver": "^7.6.3", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^6.2.1", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.0", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "dependencies": { + "@isaacs/cliui": { + "version": "8.0.2", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "bundled": true, + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "@isaacs/fs-minipass": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.4" + } + }, + "@isaacs/string-locale-compare": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "@npmcli/agent": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + } + }, + "@npmcli/arborist": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + } + }, + "@npmcli/config": { + "version": "9.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + } + }, + "@npmcli/fs": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "@npmcli/git": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + } + }, + "@npmcli/installed-package-contents": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + } + }, + "@npmcli/map-workspaces": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + } + }, + "@npmcli/metavuln-calculator": { + "version": "8.0.1", + "bundled": true, + "dev": true, + "requires": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "pacote": { + "version": "20.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + } + } + } + }, + "@npmcli/name-from-folder": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "@npmcli/node-gyp": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "@npmcli/package-json": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "normalize-package-data": "^7.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3" + } + }, + "@npmcli/promise-spawn": { + "version": "8.0.2", + "bundled": true, + "dev": true, + "requires": { + "which": "^5.0.0" + } + }, + "@npmcli/query": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "postcss-selector-parser": "^6.1.2" + } + }, + "@npmcli/redact": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "@npmcli/run-script": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + } + }, + "@pkgjs/parseargs": { + "version": "0.11.0", + "bundled": true, + "dev": true, + "optional": true + }, + "@sigstore/protobuf-specs": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "@sigstore/tuf": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^3.0.1" + } + }, + "@tufjs/canonical-json": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "abbrev": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "agent-base": { + "version": "7.1.1", + "bundled": true, + "dev": true, + "requires": { + "debug": "^4.3.4" + } + }, + "aggregate-error": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "6.2.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "bin-links": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + } + }, + "binary-extensions": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "cacache": { + "version": "19.0.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "minizlib": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + } + }, + "mkdirp": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "p-map": { + "version": "7.0.2", + "bundled": true, + "dev": true + }, + "tar": { + "version": "7.4.3", + "bundled": true, + "dev": true, + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + } + }, + "yallist": { + "version": "5.0.0", + "bundled": true, + "dev": true + } + } + }, + "chalk": { + "version": "5.3.0", + "bundled": true, + "dev": true + }, + "chownr": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "ci-info": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cidr-regex": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "ip-regex": "^5.0.0" + } + }, + "clean-stack": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "cli-columns": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "cmd-shim": { + "version": "7.0.0", + "bundled": true, + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "common-ancestor-path": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "7.0.6", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cssesc": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "debug": { + "version": "4.3.7", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "diff": { + "version": "5.2.0", + "bundled": true, + "dev": true + }, + "eastasianwidth": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true, + "dev": true + }, + "encoding": { + "version": "0.1.13", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "iconv-lite": "^0.6.2" + } + }, + "env-paths": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "err-code": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "exponential-backoff": { + "version": "3.1.1", + "bundled": true, + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.16", + "bundled": true, + "dev": true + }, + "foreground-child": { + "version": "3.3.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + } + }, + "fs-minipass": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "glob": { + "version": "10.4.5", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true, + "dev": true + }, + "hosted-git-info": { + "version": "8.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "http-cache-semantics": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "http-proxy-agent": { + "version": "7.0.2", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + } + }, + "https-proxy-agent": { + "version": "7.0.5", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^7.0.2", + "debug": "4" + } + }, + "iconv-lite": { + "version": "0.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "ignore-walk": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "minimatch": "^9.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "ini": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "init-package-json": { + "version": "7.0.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + } + }, + "ip-address": { + "version": "9.0.5", + "bundled": true, + "dev": true, + "requires": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + } + }, + "ip-regex": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "is-cidr": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "cidr-regex": "^4.1.1" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "jackspeak": { + "version": "3.4.3", + "bundled": true, + "dev": true, + "requires": { + "@isaacs/cliui": "^8.0.2", + "@pkgjs/parseargs": "^0.11.0" + } + }, + "jsbn": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "json-parse-even-better-errors": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "json-stringify-nice": { + "version": "1.1.4", + "bundled": true, + "dev": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true, + "dev": true + }, + "just-diff": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "just-diff-apply": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "libnpmaccess": { + "version": "9.0.0", + "bundled": true, + "dev": true, + "requires": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + } + }, + "libnpmdiff": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^6.2.1" + } + }, + "libnpmexec": { + "version": "9.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + } + }, + "libnpmfund": { + "version": "6.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^8.0.0" + } + }, + "libnpmhook": { + "version": "11.0.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + } + }, + "libnpmorg": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + } + }, + "libnpmpack": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/arborist": "^8.0.0", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + } + }, + "libnpmpublish": { + "version": "10.0.1", + "bundled": true, + "dev": true, + "requires": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + } + }, + "libnpmsearch": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "npm-registry-fetch": "^18.0.1" + } + }, + "libnpmteam": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + } + }, + "libnpmversion": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + } + }, + "lru-cache": { + "version": "10.4.3", + "bundled": true, + "dev": true + }, + "make-fetch-happen": { + "version": "14.0.3", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "dependencies": { + "negotiator": { + "version": "1.0.0", + "bundled": true, + "dev": true + } + } + }, + "minimatch": { + "version": "9.0.5", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "minipass": { + "version": "7.1.2", + "bundled": true, + "dev": true + }, + "minipass-collect": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "minipass-fetch": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "encoding": "^0.1.13", + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "dependencies": { + "minizlib": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + } + } + } + }, + "minipass-flush": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-pipeline": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass-sized": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minizlib": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "bundled": true, + "dev": true + }, + "ms": { + "version": "2.1.3", + "bundled": true, + "dev": true + }, + "mute-stream": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "node-gyp": { + "version": "11.0.0", + "bundled": true, + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "which": "^5.0.0" + }, + "dependencies": { + "chownr": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "minizlib": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + } + }, + "mkdirp": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "tar": { + "version": "7.4.3", + "bundled": true, + "dev": true, + "requires": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.0.1", + "mkdirp": "^3.0.1", + "yallist": "^5.0.0" + } + }, + "yallist": { + "version": "5.0.0", + "bundled": true, + "dev": true + } + } + }, + "nopt": { + "version": "8.0.0", + "bundled": true, + "dev": true, + "requires": { + "abbrev": "^2.0.0" + }, + "dependencies": { + "abbrev": { + "version": "2.0.0", + "bundled": true, + "dev": true + } + } + }, + "normalize-package-data": { + "version": "7.0.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "npm-audit-report": { + "version": "6.0.0", + "bundled": true, + "dev": true + }, + "npm-bundled": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "npm-normalize-package-bin": "^4.0.0" + } + }, + "npm-install-checks": { + "version": "7.1.1", + "bundled": true, + "dev": true, + "requires": { + "semver": "^7.1.1" + } + }, + "npm-normalize-package-bin": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "npm-package-arg": { + "version": "12.0.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + } + }, + "npm-packlist": { + "version": "9.0.0", + "bundled": true, + "dev": true, + "requires": { + "ignore-walk": "^7.0.0" + } + }, + "npm-pick-manifest": { + "version": "10.0.0", + "bundled": true, + "dev": true, + "requires": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + } + }, + "npm-profile": { + "version": "11.0.1", + "bundled": true, + "dev": true, + "requires": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + } + }, + "npm-registry-fetch": { + "version": "18.0.2", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "dependencies": { + "minizlib": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + } + } + } + }, + "npm-user-validate": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "p-map": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, + "package-json-from-dist": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "pacote": { + "version": "19.0.1", + "bundled": true, + "dev": true, + "requires": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^6.1.11" + } + }, + "parse-conflict-json": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + } + }, + "path-key": { + "version": "3.1.1", + "bundled": true, + "dev": true + }, + "path-scurry": { + "version": "1.11.1", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.1.2", + "bundled": true, + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "proc-log": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "proggy": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "promise-all-reject-late": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-call-limit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "promise-retry": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + } + }, + "promzard": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "read": "^4.0.0" + } + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "read": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "mute-stream": "^2.0.0" + } + }, + "read-cmd-shim": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "read-package-json-fast": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + } + }, + "retry": { + "version": "0.12.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "5.0.10", + "bundled": true, + "dev": true, + "requires": { + "glob": "^10.3.7" + } + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "7.6.3", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "sigstore": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^3.0.0", + "@sigstore/tuf": "^3.0.0", + "@sigstore/verify": "^2.0.0" + }, + "dependencies": { + "@sigstore/bundle": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "@sigstore/protobuf-specs": "^0.3.2" + } + }, + "@sigstore/core": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "@sigstore/sign": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^14.0.1", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + } + }, + "@sigstore/verify": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2" + } + } + } + }, + "smart-buffer": { + "version": "4.2.0", + "bundled": true, + "dev": true + }, + "socks": { + "version": "2.8.3", + "bundled": true, + "dev": true, + "requires": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + } + }, + "socks-proxy-agent": { + "version": "8.0.4", + "bundled": true, + "dev": true, + "requires": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + } + }, + "spdx-correct": { + "version": "3.2.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + }, + "dependencies": { + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + } + } + }, + "spdx-exceptions": { + "version": "2.5.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.20", + "bundled": true, + "dev": true + }, + "sprintf-js": { + "version": "1.1.3", + "bundled": true, + "dev": true + }, + "ssri": { + "version": "12.0.0", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^7.0.3" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string-width-cjs": { + "version": "npm:string-width@4.2.3", + "bundled": true, + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-ansi-cjs": { + "version": "npm:strip-ansi@6.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "9.4.0", + "bundled": true, + "dev": true + }, + "tar": { + "version": "6.2.1", + "bundled": true, + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "fs-minipass": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "bundled": true, + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + } + } + }, + "minipass": { + "version": "5.0.0", + "bundled": true, + "dev": true + } + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "treeverse": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "tuf-js": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "dependencies": { + "@tufjs/models": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + } + } + } + }, + "unique-filename": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "unique-slug": "^5.0.0" } }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "unique-slug": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, "dev": true, "requires": { - "has-flag": "^3.0.0" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + }, + "dependencies": { + "spdx-expression-parse": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + } + } + }, + "validate-npm-package-name": { + "version": "6.0.0", + "bundled": true, + "dev": true + }, + "walk-up-path": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "which": { + "version": "5.0.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^3.1.1" + }, + "dependencies": { + "isexe": { + "version": "3.1.1", + "bundled": true, + "dev": true + } + } + }, + "wrap-ansi": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "6.1.0", + "bundled": true, + "dev": true + }, + "emoji-regex": { + "version": "9.2.2", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "requires": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + } + }, + "strip-ansi": { + "version": "7.1.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^6.0.1" + } + } + } + }, + "wrap-ansi-cjs": { + "version": "npm:wrap-ansi@7.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + } + } + }, + "write-file-atomic": { + "version": "6.0.0", + "bundled": true, + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" } + }, + "yallist": { + "version": "4.0.0", + "bundled": true, + "dev": true } } }, - "noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" - } - }, - "nopt": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", - "dev": true, - "requires": { - "abbrev": "1" - } - }, - "normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", - "dev": true, - "requires": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, "npm-path": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", @@ -35417,11 +38637,40 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, + "p-each-series": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-3.0.0.tgz", + "integrity": "sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==", + "dev": true + }, + "p-filter": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-4.1.0.tgz", + "integrity": "sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==", + "dev": true, + "requires": { + "p-map": "^7.0.1" + }, + "dependencies": { + "p-map": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.3.tgz", + "integrity": "sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==", + "dev": true + } + } + }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, + "p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -35449,6 +38698,12 @@ "aggregate-error": "^3.0.0" } }, + "p-reduce": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-2.1.0.tgz", + "integrity": "sha512-2USApvnsutq8uoxZBGbbWM0JIYLiEMJ9RlaN7fAzVNb9OZN0SHjjTTfIcb667XynS5Y1VhwDJVDa72TnPzAYWw==", + "dev": true + }, "p-timeout": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", @@ -35660,12 +38915,41 @@ "lines-and-columns": "^1.1.6" } }, + "parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "requires": { + "parse5": "^6.0.1" + }, + "dependencies": { + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -35791,6 +39075,61 @@ "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", "dev": true }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true + } + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -35952,6 +39291,15 @@ } } }, + "pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "requires": { + "parse-ms": "^4.0.0" + } + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -36002,6 +39350,12 @@ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -36158,12 +39512,87 @@ } } }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "requires": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "dependencies": { + "hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "requires": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + } + }, + "parse-json": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.1.0.tgz", + "integrity": "sha512-rum1bPifK5SSar35Z6EKZuYPJx85pkNaFrxBK3mwdfSJ1/WKbYrjoW/zTPSjRRamfmVX1ACBIdFAO0VRErW/EA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.22.13", + "index-to-position": "^0.1.2", + "type-fest": "^4.7.1" + } + }, + "read-pkg": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", + "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" + } + }, + "type-fest": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.35.0.tgz", + "integrity": "sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==", + "dev": true + }, + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + } + } + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -36678,6 +40107,247 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" }, + "semantic-release": { + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.2.3.tgz", + "integrity": "sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==", + "dev": true, + "requires": { + "@semantic-release/commit-analyzer": "^13.0.0-beta.1", + "@semantic-release/error": "^4.0.0", + "@semantic-release/github": "^11.0.0", + "@semantic-release/npm": "^12.0.0", + "@semantic-release/release-notes-generator": "^14.0.0-beta.1", + "aggregate-error": "^5.0.0", + "cosmiconfig": "^9.0.0", + "debug": "^4.0.0", + "env-ci": "^11.0.0", + "execa": "^9.0.0", + "figures": "^6.0.0", + "find-versions": "^6.0.0", + "get-stream": "^6.0.0", + "git-log-parser": "^1.2.0", + "hook-std": "^3.0.0", + "hosted-git-info": "^8.0.0", + "import-from-esm": "^2.0.0", + "lodash-es": "^4.17.21", + "marked": "^12.0.0", + "marked-terminal": "^7.0.0", + "micromatch": "^4.0.2", + "p-each-series": "^3.0.0", + "p-reduce": "^3.0.0", + "read-package-up": "^11.0.0", + "resolve-from": "^5.0.0", + "semver": "^7.3.2", + "semver-diff": "^4.0.0", + "signale": "^1.2.1", + "yargs": "^17.5.1" + }, + "dependencies": { + "@semantic-release/error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", + "integrity": "sha512-mgdxrHTLOjOddRVYIYDo0fR3/v61GNN1YGkfbrjuIKg/uMgCd+Qzo3UAXJ+woLQQpos4pl5Esuw5A7AoNlzjUQ==", + "dev": true + }, + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true + }, + "aggregate-error": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", + "integrity": "sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==", + "dev": true, + "requires": { + "clean-stack": "^5.2.0", + "indent-string": "^5.0.0" + } + }, + "clean-stack": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", + "integrity": "sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==", + "dev": true, + "requires": { + "escape-string-regexp": "5.0.0" + } + }, + "cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "requires": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + } + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, + "execa": { + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" + }, + "dependencies": { + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "dev": true, + "requires": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } + } + } + }, + "figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "requires": { + "is-unicode-supported": "^2.0.0" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "hosted-git-info": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-8.0.2.tgz", + "integrity": "sha512-sYKnA7eGln5ov8T8gnYlkSOxFJvywzEx9BueN6xo/GKO8PGiI6uK6xx+DIGe45T3bdVjLAQDQW1aicT8z8JwQg==", + "dev": true, + "requires": { + "lru-cache": "^10.0.1" + } + }, + "human-signals": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", + "dev": true + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true + }, + "is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true + }, + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true + }, + "lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "dev": true, + "requires": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + } + }, + "p-reduce": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-3.0.0.tgz", + "integrity": "sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==", + "dev": true + }, + "path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true + }, + "semver-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", + "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", + "dev": true, + "requires": { + "semver": "^7.3.5" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "dev": true + }, + "typescript": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "optional": true, + "peer": true + } + } + }, "semver": { "version": "7.3.7", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", @@ -36709,6 +40379,12 @@ } } }, + "semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true + }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -36860,38 +40536,110 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "short-unique-id": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", + "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "signale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/signale/-/signale-1.4.0.tgz", + "integrity": "sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==", + "dev": true, + "requires": { + "chalk": "^2.3.2", + "figures": "^2.0.0", + "pkg-conf": "^2.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "short-unique-id": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.2.0.tgz", - "integrity": "sha512-cMGfwNyfDZ/nzJ2k2M+ClthBIh//GlZl1JEf47Uoa9XR11bz8Pa2T2wQO4bVrRdH48LrIDWJahQziKo3MjhsWg==" - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "simple-git": { "version": "3.27.0", "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.27.0.tgz", @@ -36975,6 +40723,15 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "dev": true }, + "skin-tone": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", + "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", + "dev": true, + "requires": { + "unicode-emoji-modifier-base": "^1.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -37243,6 +41000,12 @@ "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, + "spawn-error-forwarder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz", + "integrity": "sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==", + "dev": true + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -37555,6 +41318,48 @@ "resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-3.0.2.tgz", "integrity": "sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==" }, + "stream-combiner2": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", + "integrity": "sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==", + "dev": true, + "requires": { + "duplexer2": "~0.1.0", + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -37721,6 +41526,16 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dev": true, + "requires": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + } + }, "superagent": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", @@ -37802,6 +41617,33 @@ "integrity": "sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA==", "dev": true }, + "supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -37947,6 +41789,64 @@ } } }, + "temp-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", + "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", + "dev": true + }, + "tempy": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.1.0.tgz", + "integrity": "sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==", + "dev": true, + "requires": { + "is-stream": "^3.0.0", + "temp-dir": "^3.0.0", + "type-fest": "^2.12.2", + "unique-string": "^3.0.0" + }, + "dependencies": { + "crypto-random-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", + "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", + "dev": true, + "requires": { + "type-fest": "^1.0.1" + }, + "dependencies": { + "type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true + } + } + }, + "is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true + }, + "type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "dev": true + }, + "unique-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", + "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", + "dev": true, + "requires": { + "crypto-random-string": "^4.0.0" + } + } + } + }, "test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -37986,6 +41886,24 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -38023,6 +41941,15 @@ } } }, + "time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "requires": { + "convert-hrtime": "^5.0.0" + } + }, "tiny-glob": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", @@ -38137,6 +42064,12 @@ "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true }, + "traverse": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.8.tgz", + "integrity": "sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==", + "dev": true + }, "tree-kill": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", @@ -38484,6 +42417,18 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "unicode-emoji-modifier-base": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", + "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", + "dev": true + }, + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -38513,6 +42458,12 @@ "crypto-random-string": "^2.0.0" } }, + "universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -38666,6 +42617,12 @@ } } }, + "url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "dev": true + }, "url-parse-lax": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", @@ -38973,24 +42930,35 @@ "dev": true }, "yargs": { - "version": "17.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.4.1.tgz", - "integrity": "sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { - "cliui": "^7.0.2", + "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" + "yargs-parser": "^21.1.1" }, "dependencies": { + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, "yargs-parser": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz", - "integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==", + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true } } @@ -39013,6 +42981,12 @@ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true }, + "yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true + }, "yup": { "version": "0.32.11", "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.11.tgz", diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 85488d8d2..114792cc5 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -1698,7 +1698,7 @@ components: serviceParams: name: serviceName in: path - description: ID of the service + description: Name of the service required: true schema: type: string diff --git a/src/openapi/service.yaml b/src/openapi/service.yaml index 368a86473..46f840bea 100644 --- a/src/openapi/service.yaml +++ b/src/openapi/service.yaml @@ -199,5 +199,6 @@ IngressPublic: - public default: public nullable: true + type: object description: Will only accept traffic coming from an external loadbalancer. title: External diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 86fd6dcc0..8b3bba87c 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -7,7 +7,7 @@ import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' import { emptyDir, pathExists, unlink } from 'fs-extra' import { readdir, readFile, writeFile } from 'fs/promises' import { generate as generatePassword } from 'generate-password' -import { cloneDeep, filter, isEmpty, map, mapValues, omit, pick, unset } from 'lodash' +import { cloneDeep, filter, isArray, isEmpty, map, mapValues, omit, pick, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' import getRepo, { Git } from 'src/git' @@ -39,7 +39,7 @@ import { Workload, WorkloadValues, } from 'src/otomi-models' -import { arrayToObject, getValuesSchema, removeBlankAttributes } from 'src/utils' +import { arrayToObject, getServiceUrl, getValuesSchema, removeBlankAttributes } from 'src/utils' import { cleanEnv, CUSTOM_ROOT_CA, @@ -158,6 +158,56 @@ export default class OtomiStack { } } + transformServices(servicesArray: any[] = [], teamId: string): any[] { + return servicesArray.map((service) => { + const { cluster, dns } = this.getSettings(['cluster', 'dns']) + const url = getServiceUrl({ domain: service.domain, name: service.name, teamId, cluster, dns }) + + const headers = isArray(service.headers) ? undefined : service.headers + + const inService = omit(service, [ + 'certArn', + 'certName', + 'domain', + 'forwardPath', + 'hasCert', + 'paths', + 'type', + 'ownHost', + 'tlsPass', + 'ingressClassName', + 'headers', + 'useCname', + 'cname', + ]) + const svc = { + ...inService, + name: service.name, + teamId, + ingress: + service.type === 'cluster' + ? { type: 'cluster' } + : { + certArn: service.certArn || undefined, + certName: service.certName || undefined, + domain: url.domain, + headers, + forwardPath: 'forwardPath' in service, + hasCert: 'hasCert' in service, + paths: service.paths || [], + subdomain: url.subdomain, + tlsPass: 'tlsPass' in service, + type: service.type, + useDefaultHost: !service.domain && service.ownHost, + ingressClassName: service.ingressClassName || undefined, + useCname: service.useCname, + cname: service.cname, + }, + } + return removeBlankAttributes(svc) + }) + } + transformApps(appsObj: Record): App[] { if (!appsObj || typeof appsObj !== 'object') return [] @@ -191,6 +241,11 @@ export default class OtomiStack { const repo = rawRepo as Repo this.repoService = new RepoService(repo) + //TODO fix this transforming of the services + this.repoService.getRepo().teamConfig = mapValues(repo.teamConfig, (teamConfig) => ({ + ...teamConfig, + services: this.transformServices(teamConfig.services, teamConfig.settings.name), + })) } } From 8d6c6545fb8d2ac791ddc7f723bc28a677467d25 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 28 Feb 2025 09:21:41 +0100 Subject: [PATCH 27/49] fix: tests --- src/api.authz.test.ts | 2 +- src/otomi-stack.test.ts | 64 +++++++++++++------------- src/services/RepoService.ts | 4 +- src/services/TeamConfigService.test.ts | 2 +- 4 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index c9f07aa75..b68ab3dcc 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -638,7 +638,7 @@ describe('API authz tests', () => { describe('Code repository endpoints tests', () => { const data = { - label: 'demo', + name: 'demo', gitService: 'github' as 'gitea' | 'github' | 'gitlab', repositoryUrl: 'https://github.com/buildpacks/samples', private: true, diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index 27dbd7ea9..697681870 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -100,7 +100,7 @@ describe('Data validation', () => { codeRepos: [], workloads: [], services: [], - sealedSecrets: [], + sealedsecrets: [], backups: [], projects: [], netpols: [], @@ -120,7 +120,7 @@ describe('Data validation', () => { codeRepos: [], workloads: [], services: [], - sealedSecrets: [], + sealedsecrets: [], backups: [], projects: [], netpols: [], @@ -314,7 +314,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', } as CodeRepo @@ -330,7 +330,7 @@ describe('Code repositories tests', () => { test('should create an internal code repository', async () => { const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', } as CodeRepo) @@ -339,20 +339,20 @@ describe('Code repositories tests', () => { const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) expect(codeRepo).toEqual({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) expect(createItemSpy).toHaveBeenCalledWith({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) @@ -368,7 +368,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', } as CodeRepo @@ -383,7 +383,7 @@ describe('Code repositories tests', () => { const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', } as CodeRepo) @@ -393,7 +393,7 @@ describe('Code repositories tests', () => { const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) @@ -401,13 +401,13 @@ describe('Code repositories tests', () => { expect(codeRepo).toEqual({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) expect(updateItemSpy).toHaveBeenCalledWith('1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', }) @@ -423,7 +423,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'gitea', repositoryUrl: 'https://gitea.test.com', } as CodeRepo @@ -444,7 +444,7 @@ describe('Code repositories tests', () => { test('should create an external public code repository', async () => { const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', } as CodeRepo) @@ -453,20 +453,20 @@ describe('Code repositories tests', () => { const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', }) expect(codeRepo).toEqual({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', }) expect(createItemSpy).toHaveBeenCalledWith({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', }) @@ -482,7 +482,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', } as CodeRepo @@ -497,7 +497,7 @@ describe('Code repositories tests', () => { const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', } as CodeRepo) @@ -507,7 +507,7 @@ describe('Code repositories tests', () => { const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', }) @@ -515,13 +515,13 @@ describe('Code repositories tests', () => { expect(codeRepo).toEqual({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', }) expect(updateItemSpy).toHaveBeenCalledWith('1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', }) @@ -537,7 +537,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', } as CodeRepo @@ -558,7 +558,7 @@ describe('Code repositories tests', () => { test('should create an external private code repository', async () => { const createItemSpy = jest.spyOn(teamConfigService, 'createCodeRepo').mockReturnValue({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -569,7 +569,7 @@ describe('Code repositories tests', () => { const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -578,7 +578,7 @@ describe('Code repositories tests', () => { expect(codeRepo).toEqual({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -586,7 +586,7 @@ describe('Code repositories tests', () => { }) expect(createItemSpy).toHaveBeenCalledWith({ teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -604,7 +604,7 @@ describe('Code repositories tests', () => { const updateItemSpy = jest.spyOn(teamConfigService, 'updateCodeRepo').mockReturnValue({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -616,7 +616,7 @@ describe('Code repositories tests', () => { const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -626,7 +626,7 @@ describe('Code repositories tests', () => { expect(codeRepo).toEqual({ id: '1', teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -634,7 +634,7 @@ describe('Code repositories tests', () => { }) expect(updateItemSpy).toHaveBeenCalledWith('1', { teamId: 'demo', - label: 'code-1-updated', + name: 'code-1-updated', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, @@ -652,7 +652,7 @@ describe('Code repositories tests', () => { const codeRepo = { id: '1', teamId: 'demo', - label: 'code-1', + name: 'code-1', gitService: 'github', repositoryUrl: 'https://github.test.com', private: true, diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 231f628a0..9613d8665 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -131,7 +131,7 @@ export class RepoService { codeRepos: [], workloads: [], services: [], - sealedSecrets: [], + sealedsecrets: [], backups: [], projects: [], netpols: [], @@ -269,7 +269,7 @@ export class RepoService { public getAllSealedSecrets(): SealedSecret[] { return ( Object.entries(this.repo.teamConfig) - .flatMap(([teamId, config]) => config.sealedSecrets?.map((secret) => ({ ...secret, teamId }))) + .flatMap(([teamId, config]) => config.sealedsecrets?.map((secret) => ({ ...secret, teamId }))) .filter(Boolean) ?? [] ) } diff --git a/src/services/TeamConfigService.test.ts b/src/services/TeamConfigService.test.ts index 916b3c448..c9ca8ac6c 100644 --- a/src/services/TeamConfigService.test.ts +++ b/src/services/TeamConfigService.test.ts @@ -29,7 +29,7 @@ describe('TeamConfigService', () => { workloads: [], workloadValues: [], services: [], - sealedSecrets: [], + sealedsecrets: [], backups: [], projects: [], netpols: [], From 815fcc6b4e648d65197d73b2cd4d31cac05e2a17 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 28 Feb 2025 11:30:56 +0100 Subject: [PATCH 28/49] fix: saving users correctly --- src/otomi-stack.ts | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 8b3bba87c..203eac9be 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -1670,7 +1670,11 @@ export default class OtomiStack { async saveTeam(team: Team, secretPaths?: string[]): Promise { debug(`Saving team ${team.name}`) - const repo = this.createTeamConfigInRepo(team.name, 'settings', team) + debug('team', JSON.stringify(team)) + const inTeam = team + //TODO fix this issue where resource quota needs to be saved as an object + inTeam.resourceQuota = arrayToObject((team.resourceQuota as []) ?? []) as any + const repo = this.createTeamConfigInRepo(team.name, 'settings', inTeam) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSettingSet')! await this.git.saveConfigWithSecrets(repo, secretPaths ?? this.getSecretPaths(), fileMap) } @@ -1691,14 +1695,14 @@ export default class OtomiStack { const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(id) this.repoService.getTeamConfigService(teamId).deleteSealedSecret(id) - const repo = this.createTeamConfigInRepo(teamId, 'sealedSecrets', sealedSecret) + const repo = this.createTeamConfigInRepo(teamId, 'sealedSecrets', [sealedSecret]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamSecret')! await this.git.deleteConfig(repo, fileMap) } async saveTeamBackup(teamId: string, backup: Backup): Promise { debug(`Saving backup ${backup.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'backups', backup) + const repo = this.createTeamConfigInRepo(teamId, 'backups', [backup]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! await this.git.saveConfig(repo, fileMap) } @@ -1707,14 +1711,14 @@ export default class OtomiStack { const backup = this.repoService.getTeamConfigService(teamId).getBackup(name) this.repoService.getTeamConfigService(teamId).deleteBackup(name) - const repo = this.createTeamConfigInRepo(teamId, 'backups', backup) + const repo = this.createTeamConfigInRepo(teamId, 'backups', [backup]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBackup')! await this.git.deleteConfig(repo, fileMap) } async saveTeamNetpols(teamId: string, netpol: Netpol): Promise { debug(`Saving netpols ${netpol.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'netpols', netpol) + const repo = this.createTeamConfigInRepo(teamId, 'netpols', [netpol]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! await this.git.saveConfig(repo, fileMap) } @@ -1723,14 +1727,14 @@ export default class OtomiStack { const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) this.repoService.getTeamConfigService(teamId).deleteNetpol(name) - const repo = this.createTeamConfigInRepo(teamId, 'netpols', netpol) + const repo = this.createTeamConfigInRepo(teamId, 'netpols', [netpol]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamNetworkControl')! await this.git.deleteConfig(repo, fileMap) } async saveTeamWorkload(teamId: string, workload: Workload): Promise { debug(`Saving workload ${workload.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'workloads', workload) + const repo = this.createTeamConfigInRepo(teamId, 'workloads', [workload]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! await this.git.saveConfig(repo, fileMap) } @@ -1739,14 +1743,14 @@ export default class OtomiStack { const workload = this.repoService.getTeamConfigService(teamId).getWorkload(name) this.repoService.getTeamConfigService(teamId).deleteWorkload(name) - const repo = this.createTeamConfigInRepo(teamId, 'workloads', workload) + const repo = this.createTeamConfigInRepo(teamId, 'workloads', [workload]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkload')! await this.git.deleteConfig(repo, fileMap) } async saveTeamProject(teamId: string, project: Project): Promise { debug(`Saving project ${project.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'projects', project) + const repo = this.createTeamConfigInRepo(teamId, 'projects', [project]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! await this.git.saveConfig(repo, fileMap) } @@ -1755,14 +1759,14 @@ export default class OtomiStack { const project = this.repoService.getTeamConfigService(teamId).getProject(name) this.repoService.getTeamConfigService(teamId).deleteProject(name) - const repo = this.createTeamConfigInRepo(teamId, 'projects', project) + const repo = this.createTeamConfigInRepo(teamId, 'projects', [project]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamProject')! await this.git.deleteConfig(repo, fileMap) } async saveTeamBuild(teamId: string, build: Build): Promise { debug(`Saving build ${build.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'builds', build) + const repo = this.createTeamConfigInRepo(teamId, 'builds', [build]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! await this.git.saveConfig(repo, fileMap) } @@ -1771,14 +1775,14 @@ export default class OtomiStack { const build = this.repoService.getTeamConfigService(teamId).getBuild(name) this.repoService.getTeamConfigService(teamId).deleteBuild(name) - const repo = this.createTeamConfigInRepo(teamId, 'builds', build) + const repo = this.createTeamConfigInRepo(teamId, 'builds', [build]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamBuild')! await this.git.deleteConfig(repo, fileMap) } async saveTeamCodeRepo(teamId: string, codeRepo: CodeRepo): Promise { debug(`Saving codeRepo ${codeRepo.name} for team ${teamId}`) - const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) + const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', [codeRepo]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! await this.git.saveConfig(repo, fileMap) } @@ -1787,7 +1791,7 @@ export default class OtomiStack { const codeRepo = this.repoService.getTeamConfigService(teamId).getCodeRepo(label) this.repoService.getTeamConfigService(teamId).deleteCodeRepo(label) - const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', codeRepo) + const repo = this.createTeamConfigInRepo(teamId, 'codeRepos', [codeRepo]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamCodeRepo')! await this.git.deleteConfig(repo, fileMap) } @@ -1807,7 +1811,7 @@ export default class OtomiStack { const updatedWorkloadValues = cloneDeep(data) as Record updatedWorkloadValues.values = stringifyYaml(data.values, undefined, 4) - const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', updatedWorkloadValues) + const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', [updatedWorkloadValues]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! await this.git.saveConfig(repo, fileMap) } @@ -1816,7 +1820,7 @@ export default class OtomiStack { const workloadValues = this.repoService.getTeamConfigService(teamId).getWorkloadValues(name) this.repoService.getTeamConfigService(teamId).deleteWorkloadValues(name) - const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', workloadValues) + const repo = this.createTeamConfigInRepo(teamId, 'workloadValues', [workloadValues]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamWorkloadValues')! await this.git.deleteConfig(repo, fileMap) } @@ -1845,7 +1849,7 @@ export default class OtomiStack { async saveTeamService(teamId: string, service: Service): Promise { debug(`Saving service: ${service.name} teamId: ${teamId}`) const newService = this.convertDbServiceToValues(service) - const repo = this.createTeamConfigInRepo(teamId, 'services', newService) + const repo = this.createTeamConfigInRepo(teamId, 'services', [newService]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! await this.git.saveConfig(repo, fileMap) } @@ -1854,7 +1858,7 @@ export default class OtomiStack { const service = this.repoService.getTeamConfigService(teamId).getService(name) this.repoService.getTeamConfigService(teamId).deleteService(name) - const repo = this.createTeamConfigInRepo(teamId, 'services', service) + const repo = this.createTeamConfigInRepo(teamId, 'services', [service]) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplTeamService')! await this.git.deleteConfig(repo, fileMap) } @@ -1863,7 +1867,7 @@ export default class OtomiStack { return { teamConfig: { [teamId]: { - [key]: Array.isArray(value) ? value : [value], + [key]: value, }, }, } From bddad05f85e1c3098459d2746cbe4adb5a97d6a5 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 28 Feb 2025 14:42:35 +0100 Subject: [PATCH 29/49] fix: renamings and remove some es disable lines --- src/k8s_operations.ts | 4 ++-- src/middleware/session.ts | 2 -- src/openapi/api.yaml | 8 ++++---- src/otomi-stack.ts | 26 +++++++++----------------- 4 files changed, 15 insertions(+), 25 deletions(-) diff --git a/src/k8s_operations.ts b/src/k8s_operations.ts index 3f9bb6630..d5747e1b0 100644 --- a/src/k8s_operations.ts +++ b/src/k8s_operations.ts @@ -391,7 +391,7 @@ export async function getSecretValues(name: string, namespace: string): Promise< } return decodedData } catch (error) { - debug(`Failed to get secret values for ${name} in ${namespace}.`) + // debug(`Failed to get secret values for ${name} in ${namespace}.`) } } @@ -417,7 +417,7 @@ export async function getSealedSecretSyncedStatus(name: string, namespace: strin } return 'NotFound' } catch (error) { - debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) + // debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) return 'NotFound' } } diff --git a/src/middleware/session.ts b/src/middleware/session.ts index bf929396d..6f930c99b 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -4,7 +4,6 @@ import { RequestHandler } from 'express' import 'express-async-errors' import { remove } from 'fs-extra' import http from 'http' -import { cloneDeep } from 'lodash' import { join } from 'path' import { Server } from 'socket.io' import { ApiNotReadyError } from 'src/error' @@ -44,7 +43,6 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis sessions[sessionId] = new OtomiStack(editor, sessionId) // init repo without inflating db from files as its slow and we just need a copy of the db await sessions[sessionId].initGit() - sessions[sessionId].repoService.setRepo(cloneDeep(readOnlyStack.repoService.getRepo())) } else sessions[sessionId].sessionId = sessionId return sessions[sessionId] } diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 114792cc5..7d110279b 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -1642,14 +1642,14 @@ components: backupParams: name: backupName in: path - description: ID of the backup + description: Name of the backup required: true schema: type: string buildParams: name: buildName in: path - description: ID of the build + description: Name of the build required: true schema: type: string @@ -1670,7 +1670,7 @@ components: projectParams: name: projectName in: path - description: ID of the project + description: Name of the project required: true schema: type: string @@ -1719,7 +1719,7 @@ components: workloadParams: name: workloadName in: path - description: ID of the workload + description: Name of the workload required: true schema: type: string diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 203eac9be..a05aad840 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -108,11 +108,9 @@ const env = cleanEnv({ }) export const rootPath = '/tmp/otomi/values' - -export function getTeamSealedSecretsValuesRootPath(teamId: string): string { - return `env/teams/${teamId}/sealedsecrets` -} -export function getTeamSealedSecretsValuesFilePath(teamId: string, sealedSecretsName: string): string { +//TODO Move this to the repo.ts +const getClusterSettingsFilePath = 'env/settings/cluster.yaml' +function getTeamSealedSecretsValuesFilePath(teamId: string, sealedSecretsName: string): string { return `env/teams/${teamId}/sealedsecrets/${sealedSecretsName}` } @@ -260,7 +258,7 @@ export default class OtomiStack { this.git = await getRepo(path, url, env.GIT_USER, env.GIT_EMAIL, env.GIT_PASSWORD, branch) await this.git.pull() //TODO fetch this url from the repo - if (await this.git.fileExists('env/settings/cluster.yaml')) break + if (await this.git.fileExists(getClusterSettingsFilePath)) break debug(`Values are not present at ${url}:${branch}`) } catch (e) { debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) @@ -270,7 +268,7 @@ export default class OtomiStack { debug(`Trying again in ${timeoutMs} ms`) await new Promise((resolve) => setTimeout(resolve, timeoutMs)) } - // branches get a copy of the "main" branch db, so we don't need to inflate + await this.loadValues() debug(`Values are loaded for ${this.editor} in ${this.sessionId}`) } @@ -531,7 +529,7 @@ export default class OtomiStack { // Shallow merge, so only first level attributes can be replaced (values, rawValues, etc.) app = { ...app, ...data } app = this.repoService.updateApp(id, app) - await this.saveAdminApps(app) + await this.saveAdminApp(app) await this.doDeployment(['apps']) return this.repoService.getApp(id) } @@ -547,7 +545,7 @@ export default class OtomiStack { const orig = this.repoService.getApp(id) if (orig && this.canToggleApp(id)) { const app = this.repoService.updateApp(id, { enabled }) - await this.saveAdminApps(app) + await this.saveAdminApp(app) } }), ) @@ -606,7 +604,7 @@ export default class OtomiStack { } if (deploy) { await this.saveTeam(team) - //TODO do this better for the teamconfig + //TODO do this better for the teamConfig. Now it updates all the team configs of the rootstack await this.doDeployment(['teamConfig']) } return team @@ -639,7 +637,6 @@ export default class OtomiStack { async createBackup(teamId: string, data: Backup): Promise { validateBackupFields(data.name, data.ttl) try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) await this.saveTeamBackup(teamId, data) @@ -678,7 +675,6 @@ export default class OtomiStack { async createNetpol(teamId: string, data: Netpol): Promise { try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId, data) await this.doDeployment(['netpols'], teamId) @@ -747,7 +743,6 @@ export default class OtomiStack { if (existingUsersEmail.some((existingUser) => existingUser === user.email)) { throw new AlreadyExists('User email already exists') } - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const createdUser = this.repoService.createUser(user) await this.saveUser(createdUser) await this.doDeployment(['users']) @@ -811,7 +806,6 @@ export default class OtomiStack { try { if (projectNameTaken) throw new AlreadyExists(projectNameTakenPublicMessage) - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const project = this.repoService.getTeamConfigService(teamId).createProject({ ...data, teamId }) if (data.build) { await this.createBuild(teamId, data.build) @@ -964,7 +958,6 @@ export default class OtomiStack { try { const body = { ...data } if (!body.private) unset(body, 'secret') - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) await this.saveTeamCodeRepo(teamId, codeRepo) await this.doDeployment(['codeRepos'], teamId) @@ -1062,7 +1055,6 @@ export default class OtomiStack { async createBuild(teamId: string, data: Build): Promise { try { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuild(teamId, data) await this.doDeployment(['builds'], teamId) @@ -1621,7 +1613,7 @@ export default class OtomiStack { this.isLoaded = true } - async saveAdminApps(app: App, secretPaths?: string[]): Promise { + async saveAdminApp(app: App, secretPaths?: string[]): Promise { const { id, enabled, values, rawValues } = app const apps = { [id]: { From ce3e394ae48440b12db7b39c3c2f32c4812dd8ce Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Fri, 28 Feb 2025 16:29:14 +0100 Subject: [PATCH 30/49] fix: updating the rootstack correctly --- src/otomi-stack.ts | 240 +++++++++++++++++++++++------- src/services/TeamConfigService.ts | 4 + 2 files changed, 189 insertions(+), 55 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index a05aad840..98f564f0e 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -84,6 +84,7 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' +import { TeamConfigService } from './services/TeamConfigService' interface ExcludedApp extends App { managed: boolean @@ -447,19 +448,9 @@ export default class OtomiStack { settings[settingId] = removeBlankAttributes(updatedSettingsData[settingId] as Record) this.repoService.updateSettings(settings) await this.saveSettings() - await this.doDeployment([ - 'alerts', - 'cluster', - 'dns', - 'ingress', - 'kms', - 'obj', - 'oidc', - 'otomi', - 'platformBackups', - 'smtp', - 'versions', - ]) + await this.doRepoDeployment((repoService) => { + repoService.updateSettings(settings) + }) return settings } @@ -530,7 +521,9 @@ export default class OtomiStack { app = { ...app, ...data } app = this.repoService.updateApp(id, app) await this.saveAdminApp(app) - await this.doDeployment(['apps']) + await this.doRepoDeployment((repoService) => { + repoService.updateApp(id, app) + }) return this.repoService.getApp(id) } @@ -549,7 +542,15 @@ export default class OtomiStack { } }), ) - await this.doDeployment(['apps']) + + await this.doRepoDeployment((repoService) => { + ids.map((id) => { + const orig = repoService.getApp(id) + if (orig && this.canToggleApp(id)) { + repoService.updateApp(id, { enabled }) + } + }) + }) } getTeams(): Array { @@ -590,22 +591,26 @@ export default class OtomiStack { const team = teamConfig.settings const apps = getAppList() const core = this.getCore() - apps.forEach((appId) => { + const teamApps = apps.flatMap((appId) => { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) - // Admin apps are loaded by loadApps function - if (teamName !== 'admin' && (isShared || inTeamApps)) - this.repoService.getTeamConfigService(teamName).createApp({ id: appId }) + return teamName !== 'admin' && (isShared || inTeamApps) + ? [this.repoService.getTeamConfigService(teamName).createApp({ id: appId })] + : [] // Empty array removes `undefined` entries }) + const policies = getPolicies() if (!data.id) { - const policies = getPolicies() this.repoService.getTeamConfigService(teamName).updatePolicies(policies) } if (deploy) { await this.saveTeam(team) - //TODO do this better for the teamConfig. Now it updates all the team configs of the rootstack - await this.doDeployment(['teamConfig']) + + await this.doRepoDeployment((repoService) => { + repoService.createTeamConfig(teamName, data) + repoService.getTeamConfigService(teamName).setApps(teamApps) + repoService.getTeamConfigService(teamName).updatePolicies(policies) + }) } return team } @@ -613,13 +618,17 @@ export default class OtomiStack { async editTeam(id: string, data: Team): Promise { const team = this.repoService.getTeamConfigService(id).updateSettings(data) await this.saveTeam(team) - await this.doDeployment(['settings'], id) + await this.doTeamDeployment(id, (teamService) => { + teamService.updateSettings(team) + }) return team } async deleteTeam(id: string): Promise { await this.deleteTeamConfig(id) - await this.doDeployment(['teamConfig']) + await this.doRepoDeployment((repoService) => { + repoService.deleteTeamConfig(id) + }) } getTeamServices(teamId: string): Array { @@ -640,7 +649,9 @@ export default class OtomiStack { const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) await this.saveTeamBackup(teamId, data) - await this.doDeployment(['backups'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createBackup(backup) + }) return backup } catch (err) { if (err.code === 409) err.publicMessage = 'Backup name already exists' @@ -656,13 +667,17 @@ export default class OtomiStack { validateBackupFields(data.name, data.ttl) const backup = this.repoService.getTeamConfigService(teamId).updateBackup(name, data) await this.saveTeamBackup(teamId, data) - await this.doDeployment(['backups'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateBackup(name, backup) + }) return backup } async deleteBackup(teamId: string, name: string): Promise { await this.deleteTeamBackup(teamId, name) - await this.doDeployment(['backups'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteBackup(name) + }) } getTeamNetpols(teamId: string): Array { @@ -677,7 +692,9 @@ export default class OtomiStack { try { const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createNetpol(netpol) + }) return netpol } catch (err) { if (err.code === 409) err.publicMessage = 'Network policy name already exists' @@ -693,14 +710,18 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(name, data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateNetpol(name, netpol) + }) return netpol } async deleteNetpol(teamId: string, name: string): Promise { const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) await this.deleteTeamNetpol(teamId, netpol.name) - await this.doDeployment(['netpols'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteNetpol(name) + }) } getAllUsers(sessionUser: SessionUser): Array { @@ -745,7 +766,9 @@ export default class OtomiStack { } const createdUser = this.repoService.createUser(user) await this.saveUser(createdUser) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.createUser(user) + }) return createdUser } catch (err) { if (err.code === 409) err.publicMessage = 'User email already exists' @@ -758,10 +781,11 @@ export default class OtomiStack { } async editUser(id: string, data: User): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const user = this.repoService.updateUser(id, data) await this.saveUser(user) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.updateUser(id, user) + }) return user } @@ -774,7 +798,9 @@ export default class OtomiStack { throw error } await this.deleteUserFile(user) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.deleteUser(user.id!) + }) } async editTeamUsers( @@ -786,7 +812,12 @@ export default class OtomiStack { await this.saveUser(updateUser) } const users = this.repoService.getUsers() - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + for (const user of data) { + const existingUser = repoService.getUser(user.id!) + repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) + } + }) return users } @@ -817,7 +848,9 @@ export default class OtomiStack { await this.createService(teamId, data.service) } await this.saveTeamProject(teamId, data) - await this.doDeployment(['projects'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createProject(project) + }) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -922,6 +955,7 @@ export default class OtomiStack { await this.saveTeamWorkload(teamId, w) await this.saveTeamWorkloadValues(teamId, wv) await this.saveTeamService(teamId, s) + //Leave this for now as we will replace projects await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) return project } @@ -942,7 +976,13 @@ export default class OtomiStack { await this.deleteTeamService(teamId, p.service.name) } await this.deleteTeamProject(teamId, name) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteBuild(name) + teamService.deleteWorkload(name) + teamService.deleteWorkloadValues(name) + teamService.deleteService(name) + teamService.deleteProject(name) + }) } getTeamCodeRepos(teamId: string): Array { @@ -960,7 +1000,9 @@ export default class OtomiStack { if (!body.private) unset(body, 'secret') const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createCodeRepo(codeRepo) + }) return codeRepo } catch (err) { if (err.code === 409) err.publicMessage = 'Code repo label already exists' @@ -978,13 +1020,17 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, body) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateCodeRepo(name, codeRepo) + }) return codeRepo } async deleteCodeRepo(teamId: string, name: string): Promise { await this.deleteTeamCodeRepo(teamId, name) - await this.doDeployment(['codeRepos'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteCodeRepo(name) + }) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -1057,7 +1103,9 @@ export default class OtomiStack { try { const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuild(teamId, data) - await this.doDeployment(['builds'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createBuild(build) + }) return build } catch (err) { if (err.code === 409) err.publicMessage = 'Build name already exists' @@ -1073,7 +1121,9 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).updateBuild(name, data) await this.saveTeamBuild(teamId, build) - await this.doDeployment(['builds'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateBuild(name, build) + }) return build } @@ -1086,7 +1136,9 @@ export default class OtomiStack { } }) await this.deleteTeamBuild(teamId, name) - await this.doDeployment(['builds'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteBuild(name) + }) } getTeamPolicies(teamId: string): Policies { @@ -1106,7 +1158,10 @@ export default class OtomiStack { teamPolicies[policyId] = removeBlankAttributes(data) const policy = this.getPolicy(teamId, policyId) await this.saveTeamPolicies(teamId) - await this.doDeployment(['policies'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + const rootStackPolicies = teamService.getPolicies() + rootStackPolicies[policyId] = removeBlankAttributes(data) + }) return policy } @@ -1225,7 +1280,10 @@ export default class OtomiStack { .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) await this.saveTeamWorkload(teamId, data) await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloads', 'workloadValues'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createWorkload(workload) + teamService.createWorkloadValues(workloadValues) + }) return workload } catch (err) { if (err.code === 409) err.publicMessage = 'Workload name already exists' @@ -1240,7 +1298,9 @@ export default class OtomiStack { async editWorkload(teamId: string, name: string, data: Workload): Promise { const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(name, data) await this.saveTeamWorkload(teamId, workload) - await this.doDeployment(['workloads'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateWorkload(name, workload) + }) return workload } @@ -1254,7 +1314,10 @@ export default class OtomiStack { }) await this.deleteTeamWorkloadValues(teamId, name) await this.deleteTeamWorkload(teamId, name) - await this.doDeployment(['workloads', 'workloadValues'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteWorkloadValues(name) + teamService.deleteWorkload(name) + }) } async editWorkloadValues(teamId: string, name: string, data: WorkloadValues): Promise { @@ -1268,7 +1331,9 @@ export default class OtomiStack { } } await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloadValues'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateWorkloadValues(name, workloadValues) + }) return workloadValues } @@ -1285,7 +1350,9 @@ export default class OtomiStack { try { const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) await this.saveTeamService(teamId, data) - await this.doDeployment(['services'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createService(service) + }) return service } catch (err) { if (err.code === 409) err.publicMessage = 'Service name already exists' @@ -1300,7 +1367,9 @@ export default class OtomiStack { async editService(teamId: string, name: string, data: Service): Promise { const service = this.repoService.getTeamConfigService(teamId).updateService(name, data) await this.saveTeamService(teamId, service) - await this.doDeployment(['services'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.updateService(name, service) + }) return service } @@ -1315,7 +1384,9 @@ export default class OtomiStack { }) } await this.deleteTeamService(teamId, name) - await this.doDeployment(['services'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteService(name) + }) } checkPublicUrlInUse(teamId: string, data: any): void { @@ -1369,6 +1440,59 @@ export default class OtomiStack { } } + async doTeamDeployment(teamId: string, action: (teamService: TeamConfigService) => void): Promise { + const rootStack = await getSessionStack() + + try { + // Commit and push Git changes + await this.git.save(this.editor!) + + // Execute the provided action dynamically + action(rootStack.repoService.getTeamConfigService(teamId)) + + debug(`Updated root stack values with ${this.sessionId} changes`) + + // Clean up the session + await cleanSession(this.sessionId!) + + // Emit pipeline status + const sha = await rootStack.git.getCommitSha() + this.emitPipelineStatus(sha) + } catch (e) { + if (e instanceof GitPullError) await this.doRestore() + const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } + getIo().emit('db', msg) + throw e + } + } + + async doRepoDeployment(action: (repoService: RepoService) => void): Promise { + const rootStack = await getSessionStack() + + try { + // Commit and push Git changes + await this.git.save(this.editor!) + + // Execute the provided action dynamically + action(rootStack.repoService) + + debug(`Updated root stack values with ${this.sessionId} changes`) + + // Clean up the session + await cleanSession(this.sessionId!) + + // Emit pipeline status + const sha = await rootStack.git.getCommitSha() + this.emitPipelineStatus(sha) + } catch (e) { + if (e instanceof GitPullError) await this.doRestore() + const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } + getIo().emit('db', msg) + throw e + } + } + + //TODO remove this one when we remove projects async doDeployment(collectionIds?: string[], teamId?: string): Promise { const rootStack = await getSessionStack() try { @@ -1542,7 +1666,9 @@ export default class OtomiStack { .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.name) - await this.doDeployment(['sealedsecrets'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createSealedSecret(sealedSecret) + }) return sealedSecret } catch (err) { if (err.code === 409) err.publicMessage = 'SealedSecret name already exists' @@ -1566,10 +1692,12 @@ export default class OtomiStack { const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord[] const sealedSecret = this.repoService .getTeamConfigService(data.teamId!) - .createSealedSecret({ ...data, encryptedData }) + .updateSealedSecret(name, { ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, name) - await this.doDeployment(['sealedsecrets'], data.teamId) + await this.doTeamDeployment(data.teamId!, (teamService) => { + teamService.updateSealedSecret(name, sealedSecret) + }) return sealedSecret } @@ -1578,7 +1706,9 @@ export default class OtomiStack { this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${name}.yaml`) await this.git.removeFile(relativePath) - await this.doDeployment(['sealedsecrets'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.deleteSealedSecret(name) + }) } async getSealedSecret(teamId: string, name: string): Promise { diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 56b2794ac..4b3cf4084 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -434,6 +434,10 @@ export class TeamConfigService { })) } + public setApps(apps: App[]) { + this.teamConfig.apps = apps + } + // ===================================== // == POLICIES CRUD == // ===================================== From bd14603c1ad9ec5c686a44104ce92504428300a5 Mon Sep 17 00:00:00 2001 From: Jehoszafat Zimnowoda <17126497+j-zimnowoda@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:13:28 +0100 Subject: [PATCH 31/49] Revert "fix: updating the rootstack correctly" This reverts commit ce3e394ae48440b12db7b39c3c2f32c4812dd8ce. --- src/otomi-stack.ts | 240 +++++++----------------------- src/services/TeamConfigService.ts | 4 - 2 files changed, 55 insertions(+), 189 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 98f564f0e..a05aad840 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -84,7 +84,6 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' -import { TeamConfigService } from './services/TeamConfigService' interface ExcludedApp extends App { managed: boolean @@ -448,9 +447,19 @@ export default class OtomiStack { settings[settingId] = removeBlankAttributes(updatedSettingsData[settingId] as Record) this.repoService.updateSettings(settings) await this.saveSettings() - await this.doRepoDeployment((repoService) => { - repoService.updateSettings(settings) - }) + await this.doDeployment([ + 'alerts', + 'cluster', + 'dns', + 'ingress', + 'kms', + 'obj', + 'oidc', + 'otomi', + 'platformBackups', + 'smtp', + 'versions', + ]) return settings } @@ -521,9 +530,7 @@ export default class OtomiStack { app = { ...app, ...data } app = this.repoService.updateApp(id, app) await this.saveAdminApp(app) - await this.doRepoDeployment((repoService) => { - repoService.updateApp(id, app) - }) + await this.doDeployment(['apps']) return this.repoService.getApp(id) } @@ -542,15 +549,7 @@ export default class OtomiStack { } }), ) - - await this.doRepoDeployment((repoService) => { - ids.map((id) => { - const orig = repoService.getApp(id) - if (orig && this.canToggleApp(id)) { - repoService.updateApp(id, { enabled }) - } - }) - }) + await this.doDeployment(['apps']) } getTeams(): Array { @@ -591,26 +590,22 @@ export default class OtomiStack { const team = teamConfig.settings const apps = getAppList() const core = this.getCore() - const teamApps = apps.flatMap((appId) => { + apps.forEach((appId) => { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) - return teamName !== 'admin' && (isShared || inTeamApps) - ? [this.repoService.getTeamConfigService(teamName).createApp({ id: appId })] - : [] // Empty array removes `undefined` entries + // Admin apps are loaded by loadApps function + if (teamName !== 'admin' && (isShared || inTeamApps)) + this.repoService.getTeamConfigService(teamName).createApp({ id: appId }) }) - const policies = getPolicies() if (!data.id) { + const policies = getPolicies() this.repoService.getTeamConfigService(teamName).updatePolicies(policies) } if (deploy) { await this.saveTeam(team) - - await this.doRepoDeployment((repoService) => { - repoService.createTeamConfig(teamName, data) - repoService.getTeamConfigService(teamName).setApps(teamApps) - repoService.getTeamConfigService(teamName).updatePolicies(policies) - }) + //TODO do this better for the teamConfig. Now it updates all the team configs of the rootstack + await this.doDeployment(['teamConfig']) } return team } @@ -618,17 +613,13 @@ export default class OtomiStack { async editTeam(id: string, data: Team): Promise { const team = this.repoService.getTeamConfigService(id).updateSettings(data) await this.saveTeam(team) - await this.doTeamDeployment(id, (teamService) => { - teamService.updateSettings(team) - }) + await this.doDeployment(['settings'], id) return team } async deleteTeam(id: string): Promise { await this.deleteTeamConfig(id) - await this.doRepoDeployment((repoService) => { - repoService.deleteTeamConfig(id) - }) + await this.doDeployment(['teamConfig']) } getTeamServices(teamId: string): Array { @@ -649,9 +640,7 @@ export default class OtomiStack { const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) await this.saveTeamBackup(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createBackup(backup) - }) + await this.doDeployment(['backups'], teamId) return backup } catch (err) { if (err.code === 409) err.publicMessage = 'Backup name already exists' @@ -667,17 +656,13 @@ export default class OtomiStack { validateBackupFields(data.name, data.ttl) const backup = this.repoService.getTeamConfigService(teamId).updateBackup(name, data) await this.saveTeamBackup(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateBackup(name, backup) - }) + await this.doDeployment(['backups'], teamId) return backup } async deleteBackup(teamId: string, name: string): Promise { await this.deleteTeamBackup(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteBackup(name) - }) + await this.doDeployment(['backups'], teamId) } getTeamNetpols(teamId: string): Array { @@ -692,9 +677,7 @@ export default class OtomiStack { try { const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createNetpol(netpol) - }) + await this.doDeployment(['netpols'], teamId) return netpol } catch (err) { if (err.code === 409) err.publicMessage = 'Network policy name already exists' @@ -710,18 +693,14 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(name, data) await this.saveTeamNetpols(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateNetpol(name, netpol) - }) + await this.doDeployment(['netpols'], teamId) return netpol } async deleteNetpol(teamId: string, name: string): Promise { const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) await this.deleteTeamNetpol(teamId, netpol.name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteNetpol(name) - }) + await this.doDeployment(['netpols'], teamId) } getAllUsers(sessionUser: SessionUser): Array { @@ -766,9 +745,7 @@ export default class OtomiStack { } const createdUser = this.repoService.createUser(user) await this.saveUser(createdUser) - await this.doRepoDeployment((repoService) => { - repoService.createUser(user) - }) + await this.doDeployment(['users']) return createdUser } catch (err) { if (err.code === 409) err.publicMessage = 'User email already exists' @@ -781,11 +758,10 @@ export default class OtomiStack { } async editUser(id: string, data: User): Promise { + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const user = this.repoService.updateUser(id, data) await this.saveUser(user) - await this.doRepoDeployment((repoService) => { - repoService.updateUser(id, user) - }) + await this.doDeployment(['users']) return user } @@ -798,9 +774,7 @@ export default class OtomiStack { throw error } await this.deleteUserFile(user) - await this.doRepoDeployment((repoService) => { - repoService.deleteUser(user.id!) - }) + await this.doDeployment(['users']) } async editTeamUsers( @@ -812,12 +786,7 @@ export default class OtomiStack { await this.saveUser(updateUser) } const users = this.repoService.getUsers() - await this.doRepoDeployment((repoService) => { - for (const user of data) { - const existingUser = repoService.getUser(user.id!) - repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) - } - }) + await this.doDeployment(['users']) return users } @@ -848,9 +817,7 @@ export default class OtomiStack { await this.createService(teamId, data.service) } await this.saveTeamProject(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createProject(project) - }) + await this.doDeployment(['projects'], teamId) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -955,7 +922,6 @@ export default class OtomiStack { await this.saveTeamWorkload(teamId, w) await this.saveTeamWorkloadValues(teamId, wv) await this.saveTeamService(teamId, s) - //Leave this for now as we will replace projects await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) return project } @@ -976,13 +942,7 @@ export default class OtomiStack { await this.deleteTeamService(teamId, p.service.name) } await this.deleteTeamProject(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteBuild(name) - teamService.deleteWorkload(name) - teamService.deleteWorkloadValues(name) - teamService.deleteService(name) - teamService.deleteProject(name) - }) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) } getTeamCodeRepos(teamId: string): Array { @@ -1000,9 +960,7 @@ export default class OtomiStack { if (!body.private) unset(body, 'secret') const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createCodeRepo(codeRepo) - }) + await this.doDeployment(['codeRepos'], teamId) return codeRepo } catch (err) { if (err.code === 409) err.publicMessage = 'Code repo label already exists' @@ -1020,17 +978,13 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, body) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateCodeRepo(name, codeRepo) - }) + await this.doDeployment(['codeRepos'], teamId) return codeRepo } async deleteCodeRepo(teamId: string, name: string): Promise { await this.deleteTeamCodeRepo(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteCodeRepo(name) - }) + await this.doDeployment(['codeRepos'], teamId) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -1103,9 +1057,7 @@ export default class OtomiStack { try { const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuild(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createBuild(build) - }) + await this.doDeployment(['builds'], teamId) return build } catch (err) { if (err.code === 409) err.publicMessage = 'Build name already exists' @@ -1121,9 +1073,7 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).updateBuild(name, data) await this.saveTeamBuild(teamId, build) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateBuild(name, build) - }) + await this.doDeployment(['builds'], teamId) return build } @@ -1136,9 +1086,7 @@ export default class OtomiStack { } }) await this.deleteTeamBuild(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteBuild(name) - }) + await this.doDeployment(['builds'], teamId) } getTeamPolicies(teamId: string): Policies { @@ -1158,10 +1106,7 @@ export default class OtomiStack { teamPolicies[policyId] = removeBlankAttributes(data) const policy = this.getPolicy(teamId, policyId) await this.saveTeamPolicies(teamId) - await this.doTeamDeployment(teamId, (teamService) => { - const rootStackPolicies = teamService.getPolicies() - rootStackPolicies[policyId] = removeBlankAttributes(data) - }) + await this.doDeployment(['policies'], teamId) return policy } @@ -1280,10 +1225,7 @@ export default class OtomiStack { .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) await this.saveTeamWorkload(teamId, data) await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createWorkload(workload) - teamService.createWorkloadValues(workloadValues) - }) + await this.doDeployment(['workloads', 'workloadValues'], teamId) return workload } catch (err) { if (err.code === 409) err.publicMessage = 'Workload name already exists' @@ -1298,9 +1240,7 @@ export default class OtomiStack { async editWorkload(teamId: string, name: string, data: Workload): Promise { const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(name, data) await this.saveTeamWorkload(teamId, workload) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateWorkload(name, workload) - }) + await this.doDeployment(['workloads'], teamId) return workload } @@ -1314,10 +1254,7 @@ export default class OtomiStack { }) await this.deleteTeamWorkloadValues(teamId, name) await this.deleteTeamWorkload(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteWorkloadValues(name) - teamService.deleteWorkload(name) - }) + await this.doDeployment(['workloads', 'workloadValues'], teamId) } async editWorkloadValues(teamId: string, name: string, data: WorkloadValues): Promise { @@ -1331,9 +1268,7 @@ export default class OtomiStack { } } await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateWorkloadValues(name, workloadValues) - }) + await this.doDeployment(['workloadValues'], teamId) return workloadValues } @@ -1350,9 +1285,7 @@ export default class OtomiStack { try { const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) await this.saveTeamService(teamId, data) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createService(service) - }) + await this.doDeployment(['services'], teamId) return service } catch (err) { if (err.code === 409) err.publicMessage = 'Service name already exists' @@ -1367,9 +1300,7 @@ export default class OtomiStack { async editService(teamId: string, name: string, data: Service): Promise { const service = this.repoService.getTeamConfigService(teamId).updateService(name, data) await this.saveTeamService(teamId, service) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.updateService(name, service) - }) + await this.doDeployment(['services'], teamId) return service } @@ -1384,9 +1315,7 @@ export default class OtomiStack { }) } await this.deleteTeamService(teamId, name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteService(name) - }) + await this.doDeployment(['services'], teamId) } checkPublicUrlInUse(teamId: string, data: any): void { @@ -1440,59 +1369,6 @@ export default class OtomiStack { } } - async doTeamDeployment(teamId: string, action: (teamService: TeamConfigService) => void): Promise { - const rootStack = await getSessionStack() - - try { - // Commit and push Git changes - await this.git.save(this.editor!) - - // Execute the provided action dynamically - action(rootStack.repoService.getTeamConfigService(teamId)) - - debug(`Updated root stack values with ${this.sessionId} changes`) - - // Clean up the session - await cleanSession(this.sessionId!) - - // Emit pipeline status - const sha = await rootStack.git.getCommitSha() - this.emitPipelineStatus(sha) - } catch (e) { - if (e instanceof GitPullError) await this.doRestore() - const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } - getIo().emit('db', msg) - throw e - } - } - - async doRepoDeployment(action: (repoService: RepoService) => void): Promise { - const rootStack = await getSessionStack() - - try { - // Commit and push Git changes - await this.git.save(this.editor!) - - // Execute the provided action dynamically - action(rootStack.repoService) - - debug(`Updated root stack values with ${this.sessionId} changes`) - - // Clean up the session - await cleanSession(this.sessionId!) - - // Emit pipeline status - const sha = await rootStack.git.getCommitSha() - this.emitPipelineStatus(sha) - } catch (e) { - if (e instanceof GitPullError) await this.doRestore() - const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } - getIo().emit('db', msg) - throw e - } - } - - //TODO remove this one when we remove projects async doDeployment(collectionIds?: string[], teamId?: string): Promise { const rootStack = await getSessionStack() try { @@ -1666,9 +1542,7 @@ export default class OtomiStack { .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.name) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.createSealedSecret(sealedSecret) - }) + await this.doDeployment(['sealedsecrets'], teamId) return sealedSecret } catch (err) { if (err.code === 409) err.publicMessage = 'SealedSecret name already exists' @@ -1692,12 +1566,10 @@ export default class OtomiStack { const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord[] const sealedSecret = this.repoService .getTeamConfigService(data.teamId!) - .updateSealedSecret(name, { ...data, encryptedData }) + .createSealedSecret({ ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, name) - await this.doTeamDeployment(data.teamId!, (teamService) => { - teamService.updateSealedSecret(name, sealedSecret) - }) + await this.doDeployment(['sealedsecrets'], data.teamId) return sealedSecret } @@ -1706,9 +1578,7 @@ export default class OtomiStack { this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${name}.yaml`) await this.git.removeFile(relativePath) - await this.doTeamDeployment(teamId, (teamService) => { - teamService.deleteSealedSecret(name) - }) + await this.doDeployment(['sealedsecrets'], teamId) } async getSealedSecret(teamId: string, name: string): Promise { diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 4b3cf4084..56b2794ac 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -434,10 +434,6 @@ export class TeamConfigService { })) } - public setApps(apps: App[]) { - this.teamConfig.apps = apps - } - // ===================================== // == POLICIES CRUD == // ===================================== From e398b286b7ade15a9273514d0cb971ff6ac877c9 Mon Sep 17 00:00:00 2001 From: Jehoszafat Zimnowoda <17126497+j-zimnowoda@users.noreply.github.com> Date: Mon, 3 Mar 2025 14:47:39 +0100 Subject: [PATCH 32/49] feat: update repos.ts --- src/repo.ts | 234 ++++++++++++++++++++++------------------------------ 1 file changed, 97 insertions(+), 137 deletions(-) diff --git a/src/repo.ts b/src/repo.ts index aee73a9c7..f1f577771 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -1,52 +1,53 @@ -// IMPORTANT DON'T CHANGE THIS FILE. It's a copy of the one in apl-core import { rmSync } from 'fs' -import { pathExists } from 'fs-extra' -import { rm, writeFile } from 'fs/promises' +import { rm } from 'fs/promises' import { globSync } from 'glob' import jsonpath from 'jsonpath' import { cloneDeep, get, merge, omit, set } from 'lodash' import path from 'path' -import { getDirNames, loadYaml, objectToYaml } from './utils' +import { getDirNames, loadYaml } from './utils' export async function getTeamNames(envDir: string): Promise> { const teamsDir = path.join(envDir, 'env', 'teams') return await getDirNames(teamsDir, { skipHidden: true }) } +export type AplKind = + | 'AplApp' + | 'AplAlertSet' + | 'AplCluster' + | 'AplDatabase' + | 'AplDns' + | 'AplIngress' + | 'AplObjectStorage' + | 'AplKms' + | 'AplIdentityProvider' + | 'AplCapabilitySet' + | 'AplSmtp' + | 'AplBackupCollection' + | 'AplUser' + | 'AplTeamCodeRepo' + | 'AplTeamBuild' + | 'AplTeamPolicy' + | 'AplTeamSettingSet' + | 'AplTeamNetworkControl' + | 'AplTeamProject' + | 'AplTeamBackup' + | 'AplTeamSecret' + | 'AplTeamService' + | 'AplTeamWorkload' + | 'AplTeamWorkloadValues' + | 'AplTeamTool' + | 'AplVersion' + export interface FileMap { envDir: string - kind: - | 'AplApp' - | 'AplAlertSet' - | 'AplCluster' - | 'AplDatabase' - | 'AplDns' - | 'AplIngress' - | 'AplObjectStorage' - | 'AplKms' - | 'AplIdentityProvider' - | 'AplCapabilitySet' - | 'AplSmtp' - | 'AplBackupCollection' - | 'AplUser' - | 'AplTeamCodeRepo' - | 'AplTeamBuild' - | 'AplTeamPolicy' - | 'AplTeamSettingSet' - | 'AplTeamNetworkControl' - | 'AplTeamProject' - | 'AplTeamBackup' - | 'AplTeamSecret' - | 'AplTeamService' - | 'AplTeamWorkload' - | 'AplTeamWorkloadValues' - | 'AplTeamTool' - | 'AplVersion' + kind: AplKind jsonPathExpression: string pathGlob: string processAs: 'arrayItem' | 'mapItem' resourceGroup: 'team' | 'platformSettings' | 'platformApps' | 'platformDatabases' | 'platformBackups' | 'users' resourceDir: string + loadToSpec: boolean } export function getResourceFileName(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { @@ -105,8 +106,32 @@ export function getFilePath( return path.normalize(filePath) } +export function extractTeamDirectory(filePath: string): string { + const match = filePath.match(/\/teams\/([^/]+)/) + if (match === null) throw new Error(`Cannot extract team name from ${filePath} string`) + return match[1] +} + +export function getJsonPath(fileMap: FileMap, filePath: string): string { + let { jsonPathExpression: jsonPath } = fileMap + + if (fileMap.resourceGroup === 'team') { + const teamName = extractTeamDirectory(filePath) + jsonPath = jsonPath.replace('teamConfig.*', `teamConfig.${teamName}`) + } + + if (jsonPath.includes('.*')) { + const fileName = path.basename(filePath, path.extname(filePath)) + const strippedFileName = fileName.replace(/^secrets\.|\.yaml|\.dec$/g, '') + jsonPath = jsonPath.replace('.*', `.${strippedFileName}`) + } + if (jsonPath.includes('[*]')) jsonPath = jsonPath.replace('[*]', '') + jsonPath = jsonPath.replace('$.', '') + return jsonPath +} + export function getFileMaps(envDir: string): Array { - return [ + const maps: Array = [ { kind: 'AplApp', envDir, @@ -115,6 +140,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformApps', resourceDir: 'apps', + loadToSpec: true, }, { envDir, @@ -124,6 +150,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplCluster', @@ -133,6 +160,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplDatabase', @@ -142,6 +170,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformDatabases', resourceDir: 'databases', + loadToSpec: true, }, { kind: 'AplDns', @@ -151,6 +180,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplIngress', @@ -160,6 +190,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplKms', @@ -169,6 +200,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplObjectStorage', @@ -178,6 +210,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplIdentityProvider', @@ -187,6 +220,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplCapabilitySet', @@ -196,6 +230,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplBackupCollection', @@ -205,6 +240,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformBackups', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplSmtp', @@ -214,6 +250,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplUser', @@ -223,6 +260,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'users', resourceDir: 'users', + loadToSpec: true, }, { kind: 'AplVersion', @@ -232,15 +270,17 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'platformSettings', resourceDir: 'settings', + loadToSpec: true, }, { kind: 'AplTeamCodeRepo', envDir, - jsonPathExpression: '$.teamConfig.*.codeRepos[*]', - pathGlob: `${envDir}/env/teams/*/codeRepos/*.yaml`, + jsonPathExpression: '$.teamConfig.*.coderepos[*]', + pathGlob: `${envDir}/env/teams/*/coderepos/*.yaml`, processAs: 'arrayItem', resourceGroup: 'team', - resourceDir: 'codeRepos', + resourceDir: 'coderepos', + loadToSpec: false, }, { kind: 'AplTeamBuild', @@ -250,6 +290,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'builds', + loadToSpec: true, }, { kind: 'AplTeamWorkload', @@ -259,6 +300,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'workloads', + loadToSpec: true, }, { kind: 'AplTeamWorkloadValues', @@ -268,6 +310,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'workloadValues', + loadToSpec: false, }, { kind: 'AplTeamService', @@ -277,6 +320,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'services', + loadToSpec: true, }, { kind: 'AplTeamSecret', @@ -286,6 +330,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'sealedsecrets', + loadToSpec: false, }, { kind: 'AplTeamBackup', @@ -295,6 +340,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'backups', + loadToSpec: true, }, { kind: 'AplTeamProject', @@ -304,6 +350,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'projects', + loadToSpec: true, }, { kind: 'AplTeamNetworkControl', @@ -313,6 +360,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'arrayItem', resourceGroup: 'team', resourceDir: 'netpols', + loadToSpec: true, }, { kind: 'AplTeamSettingSet', @@ -322,6 +370,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'team', resourceDir: '.', + loadToSpec: true, }, { kind: 'AplTeamTool', @@ -331,6 +380,7 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'team', resourceDir: '.', + loadToSpec: true, }, { kind: 'AplTeamPolicy', @@ -340,14 +390,22 @@ export function getFileMaps(envDir: string): Array { processAs: 'mapItem', resourceGroup: 'team', resourceDir: '.', + loadToSpec: true, }, ] + return maps } export function hasCorrespondingDecryptedFile(filePath: string, fileList: Array): boolean { return fileList.includes(`${filePath}.dec`) } +export function getFileMap(kind: AplKind, envDir: string): FileMap { + const fileMaps = getFileMaps(envDir) + const fileMapFiltered = fileMaps.find((fileMap) => fileMap.kind === kind) + return fileMapFiltered! +} + export function renderManifest(fileMap: FileMap, jsonPath: jsonpath.PathComponent[], data: Record) { //TODO remove this custom workaround for workloadValues const manifest = @@ -375,62 +433,6 @@ export function renderManifestForSecrets(fileMap: FileMap, data: Record, -// valuesSecrets: Record, -// deps = { writeValuesToFile }, -// ): Promise { -// const jsonPathsValuesPublic = jsonpath.nodes(valuesPublic, fileMap.jsonPathExpression) -// const jsonPathsvaluesSecrets = jsonpath.nodes(valuesSecrets, fileMap.jsonPathExpression) -// -// await Promise.all( -// jsonPathsValuesPublic.map(async (node) => { -// const nodePath = node.path -// const nodeValue = node.value -// try { -// const filePath = getFilePath(fileMap, nodePath, nodeValue, '') -// const manifest = renderManifest(fileMap, nodePath, nodeValue) -// await deps.writeValuesToFile(filePath, manifest) -// } catch (e) { -// console.log(nodePath) -// console.log(fileMap) -// throw e -// } -// }), -// ) -// -// await Promise.all( -// jsonPathsvaluesSecrets.map(async (node) => { -// const nodePath = node.path -// const nodeValue = node.value -// try { -// const filePath = getFilePath(fileMap, nodePath, nodeValue, 'secrets.') -// const manifest = renderManifestForSecrets(fileMap, nodeValue) -// await deps.writeValuesToFile(filePath, manifest) -// } catch (e) { -// console.log(nodePath) -// console.log(fileMap) -// throw e -// } -// }), -// ) -// } -// export async function saveValues( -// envDir: string, -// valuesPublic: Record, -// valuesSecrets: Record, -// deps = { saveResourceGroupToFiles }, -// ): Promise { -// const fileMaps = getFileMaps(envDir) -// await Promise.all( -// fileMaps.map(async (fileMap) => { -// await deps.saveResourceGroupToFiles(fileMap, valuesPublic, valuesSecrets) -// }), -// ) -// } - export async function unsetValuesFile(envDir: string): Promise { const valuesPath = path.join(envDir, 'values-repo.yaml') await rm(valuesPath, { force: true }) @@ -443,30 +445,6 @@ export function unsetValuesFileSync(envDir: string): string { return valuesPath } -export function extractTeamDirectory(filePath: string): string { - const match = filePath.match(/\/teams\/([^/]+)/) - if (match === null) throw new Error(`Cannot extract team name from ${filePath} string`) - return match[1] -} - -export function getJsonPath(fileMap: FileMap, filePath: string): string { - let { jsonPathExpression: jsonPath } = fileMap - - if (fileMap.resourceGroup === 'team') { - const teamName = extractTeamDirectory(filePath) - jsonPath = jsonPath.replace('teamConfig.*', `teamConfig.${teamName}`) - } - - if (jsonPath.includes('.*')) { - const fileName = path.basename(filePath, path.extname(filePath)) - const strippedFileName = fileName.replace(/^secrets\.|\.yaml|\.dec$/g, '') - jsonPath = jsonPath.replace('.*', `.${strippedFileName}`) - } - if (jsonPath.includes('[*]')) jsonPath = jsonPath.replace('[*]', '') - jsonPath = jsonPath.replace('$.', '') - return jsonPath -} - export async function loadFileToSpec( filePath: string, fileMap: FileMap, @@ -477,16 +455,7 @@ export async function loadFileToSpec( const data = await deps.loadYaml(filePath) if (fileMap.processAs === 'arrayItem') { const ref: Record[] = get(spec, jsonPath) - //TODO remove this custom workaround for workloadValues as it has no spec - if (fileMap.kind === 'AplTeamWorkloadValues') { - const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] - ref.push({ ...data, name }) - } else if (fileMap.kind === 'AplTeamSecret') { - const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] - ref.push({ ...data?.spec, name }) - } else { - ref.push(data?.spec) - } + ref.push(data?.spec) } else { const ref: Record = get(spec, jsonPath) // Decrypted secrets may need to be merged with plain text specs @@ -526,7 +495,7 @@ export async function loadToSpec( } export async function loadValues(envDir: string, deps = { loadToSpec }): Promise> { - const fileMaps = getFileMaps(envDir) + const fileMaps = getFileMaps(envDir).filter((map) => map.loadToSpec === true) const spec = {} await Promise.all( @@ -538,17 +507,8 @@ export async function loadValues(envDir: string, deps = { loadToSpec }): Promise } export async function getKmsSettings(envDir: string, deps = { loadToSpec }): Promise> { - const fileMap = getFileMaps(envDir) - const kmsFiles = fileMap.find((item) => item.jsonPathExpression === '$.kms') + const kmsFiles = getFileMap('AplKms', envDir) const spec = {} - await deps.loadToSpec(spec, kmsFiles!) + await deps.loadToSpec(spec, kmsFiles) return spec } - -export async function setValuesFile(envDir: string, deps = { pathExists, loadValues, writeFile }): Promise { - const valuesPath = path.join(envDir, 'values-repo.yaml') - if (await deps.pathExists(valuesPath)) return valuesPath - const allValues = await deps.loadValues(envDir) - await deps.writeFile(valuesPath, objectToYaml(allValues)) - return valuesPath -} From d4cf237f8480f92e2d18dfd6e9d10f15ed561aad Mon Sep 17 00:00:00 2001 From: Jehoszafat Zimnowoda <17126497+j-zimnowoda@users.noreply.github.com> Date: Mon, 3 Mar 2025 16:39:57 +0100 Subject: [PATCH 33/49] feat: only encrypt data when its is necessary --- src/git.ts | 16 +++++++----- src/otomi-stack.test.ts | 18 +++++++------- src/otomi-stack.ts | 54 ++++++++++++++++++++--------------------- 3 files changed, 46 insertions(+), 42 deletions(-) diff --git a/src/git.ts b/src/git.ts index c66de7dbe..478592476 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,9 +1,12 @@ import axios, { AxiosResponse } from 'axios' import Debug from 'debug' import diff from 'deep-diff' +import { rmSync } from 'fs' import { copy, ensureDir, pathExists, readFile, writeFile } from 'fs-extra' import { unlink } from 'fs/promises' +import { glob } from 'glob' import stringifyJson from 'json-stable-stringify' +import jsonpath from 'jsonpath' import { cloneDeep, get, isEmpty, merge, set, unset } from 'lodash' import { basename, dirname, join } from 'path' import simpleGit, { CheckRepoActions, CleanOptions, CommitResult, ResetMode, SimpleGit } from 'simple-git' @@ -13,11 +16,8 @@ import { BASEURL } from './constants' import { GitPullError, HttpError, ValidationError } from './error' import { DbMessage, getIo } from './middleware' import { Core } from './otomi-models' -import { removeBlankAttributes } from './utils' import { FileMap, getFilePath, renderManifest, renderManifestForSecrets } from './repo' -import jsonpath from 'jsonpath' -import { rmSync } from 'fs' -import { glob } from 'glob' +import { removeBlankAttributes } from './utils' const debug = Debug('otomi:repo') @@ -415,10 +415,14 @@ export class Git { return this.git.revparse('HEAD') } - async save(editor: string): Promise { + async save(editor: string, encryptSecrets = true): Promise { // prepare values first try { - await this.requestPrepareValues() + if (encryptSecrets) { + await this.requestPrepareValues() + } else { + debug(`Data does not need to be encrypted`) + } } catch (e) { debug(`ERROR: ${JSON.stringify(e)}`) if (e.response) { diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index 697681870..5c4083425 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -357,7 +357,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://gitea.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -412,7 +412,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://gitea.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -435,7 +435,7 @@ describe('Code repositories tests', () => { await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() @@ -471,7 +471,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://github.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -526,7 +526,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://github.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -549,7 +549,7 @@ describe('Code repositories tests', () => { await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() @@ -593,7 +593,7 @@ describe('Code repositories tests', () => { secret: 'test', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -641,7 +641,7 @@ describe('Code repositories tests', () => { secret: 'test', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -666,7 +666,7 @@ describe('Code repositories tests', () => { await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo') + expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index a05aad840..e297249bc 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -656,13 +656,13 @@ export default class OtomiStack { validateBackupFields(data.name, data.ttl) const backup = this.repoService.getTeamConfigService(teamId).updateBackup(name, data) await this.saveTeamBackup(teamId, data) - await this.doDeployment(['backups'], teamId) + await this.doDeployment(['backups'], teamId, false) return backup } async deleteBackup(teamId: string, name: string): Promise { await this.deleteTeamBackup(teamId, name) - await this.doDeployment(['backups'], teamId) + await this.doDeployment(['backups'], teamId, false) } getTeamNetpols(teamId: string): Array { @@ -677,7 +677,7 @@ export default class OtomiStack { try { const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId) + await this.doDeployment(['netpols'], teamId, false) return netpol } catch (err) { if (err.code === 409) err.publicMessage = 'Network policy name already exists' @@ -693,14 +693,14 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(name, data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId) + await this.doDeployment(['netpols'], teamId, false) return netpol } async deleteNetpol(teamId: string, name: string): Promise { const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) await this.deleteTeamNetpol(teamId, netpol.name) - await this.doDeployment(['netpols'], teamId) + await this.doDeployment(['netpols'], teamId, false) } getAllUsers(sessionUser: SessionUser): Array { @@ -817,7 +817,7 @@ export default class OtomiStack { await this.createService(teamId, data.service) } await this.saveTeamProject(teamId, data) - await this.doDeployment(['projects'], teamId) + await this.doDeployment(['projects'], teamId, false) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -922,7 +922,7 @@ export default class OtomiStack { await this.saveTeamWorkload(teamId, w) await this.saveTeamWorkloadValues(teamId, wv) await this.saveTeamService(teamId, s) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId, false) return project } @@ -942,7 +942,7 @@ export default class OtomiStack { await this.deleteTeamService(teamId, p.service.name) } await this.deleteTeamProject(teamId, name) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId) + await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId, false) } getTeamCodeRepos(teamId: string): Array { @@ -960,7 +960,7 @@ export default class OtomiStack { if (!body.private) unset(body, 'secret') const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId) + await this.doDeployment(['codeRepos'], teamId, false) return codeRepo } catch (err) { if (err.code === 409) err.publicMessage = 'Code repo label already exists' @@ -978,13 +978,13 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, body) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId) + await this.doDeployment(['codeRepos'], teamId, false) return codeRepo } async deleteCodeRepo(teamId: string, name: string): Promise { await this.deleteTeamCodeRepo(teamId, name) - await this.doDeployment(['codeRepos'], teamId) + await this.doDeployment(['codeRepos'], teamId, false) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -1057,7 +1057,7 @@ export default class OtomiStack { try { const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuild(teamId, data) - await this.doDeployment(['builds'], teamId) + await this.doDeployment(['builds'], teamId, false) return build } catch (err) { if (err.code === 409) err.publicMessage = 'Build name already exists' @@ -1073,7 +1073,7 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).updateBuild(name, data) await this.saveTeamBuild(teamId, build) - await this.doDeployment(['builds'], teamId) + await this.doDeployment(['builds'], teamId, false) return build } @@ -1086,7 +1086,7 @@ export default class OtomiStack { } }) await this.deleteTeamBuild(teamId, name) - await this.doDeployment(['builds'], teamId) + await this.doDeployment(['builds'], teamId, false) } getTeamPolicies(teamId: string): Policies { @@ -1106,7 +1106,7 @@ export default class OtomiStack { teamPolicies[policyId] = removeBlankAttributes(data) const policy = this.getPolicy(teamId, policyId) await this.saveTeamPolicies(teamId) - await this.doDeployment(['policies'], teamId) + await this.doDeployment(['policies'], teamId, false) return policy } @@ -1225,7 +1225,7 @@ export default class OtomiStack { .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) await this.saveTeamWorkload(teamId, data) await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloads', 'workloadValues'], teamId) + await this.doDeployment(['workloads', 'workloadValues'], teamId, false) return workload } catch (err) { if (err.code === 409) err.publicMessage = 'Workload name already exists' @@ -1240,7 +1240,7 @@ export default class OtomiStack { async editWorkload(teamId: string, name: string, data: Workload): Promise { const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(name, data) await this.saveTeamWorkload(teamId, workload) - await this.doDeployment(['workloads'], teamId) + await this.doDeployment(['workloads'], teamId, false) return workload } @@ -1254,7 +1254,7 @@ export default class OtomiStack { }) await this.deleteTeamWorkloadValues(teamId, name) await this.deleteTeamWorkload(teamId, name) - await this.doDeployment(['workloads', 'workloadValues'], teamId) + await this.doDeployment(['workloads', 'workloadValues'], teamId, false) } async editWorkloadValues(teamId: string, name: string, data: WorkloadValues): Promise { @@ -1268,7 +1268,7 @@ export default class OtomiStack { } } await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloadValues'], teamId) + await this.doDeployment(['workloadValues'], teamId, false) return workloadValues } @@ -1285,7 +1285,7 @@ export default class OtomiStack { try { const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) await this.saveTeamService(teamId, data) - await this.doDeployment(['services'], teamId) + await this.doDeployment(['services'], teamId, false) return service } catch (err) { if (err.code === 409) err.publicMessage = 'Service name already exists' @@ -1300,7 +1300,7 @@ export default class OtomiStack { async editService(teamId: string, name: string, data: Service): Promise { const service = this.repoService.getTeamConfigService(teamId).updateService(name, data) await this.saveTeamService(teamId, service) - await this.doDeployment(['services'], teamId) + await this.doDeployment(['services'], teamId, false) return service } @@ -1315,7 +1315,7 @@ export default class OtomiStack { }) } await this.deleteTeamService(teamId, name) - await this.doDeployment(['services'], teamId) + await this.doDeployment(['services'], teamId, false) } checkPublicUrlInUse(teamId: string, data: any): void { @@ -1369,11 +1369,11 @@ export default class OtomiStack { } } - async doDeployment(collectionIds?: string[], teamId?: string): Promise { + async doDeployment(collectionIds: string[], teamId?: string, encryptSecrets = true): Promise { const rootStack = await getSessionStack() try { // Commit and push Git changes - await this.git.save(this.editor!) + await this.git.save(this.editor!, encryptSecrets) if (collectionIds) { collectionIds.forEach((collectionId) => { @@ -1542,7 +1542,7 @@ export default class OtomiStack { .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.name) - await this.doDeployment(['sealedsecrets'], teamId) + await this.doDeployment(['sealedsecrets'], teamId, false) return sealedSecret } catch (err) { if (err.code === 409) err.publicMessage = 'SealedSecret name already exists' @@ -1569,7 +1569,7 @@ export default class OtomiStack { .createSealedSecret({ ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, name) - await this.doDeployment(['sealedsecrets'], data.teamId) + await this.doDeployment(['sealedsecrets'], data.teamId, false) return sealedSecret } @@ -1578,7 +1578,7 @@ export default class OtomiStack { this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${name}.yaml`) await this.git.removeFile(relativePath) - await this.doDeployment(['sealedsecrets'], teamId) + await this.doDeployment(['sealedsecrets'], teamId, false) } async getSealedSecret(teamId: string, name: string): Promise { From 5e82e694f429c438e9324b8e5abfe7f77769a7ec Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 4 Mar 2025 10:23:05 +0100 Subject: [PATCH 34/49] Revert "Revert "fix: updating the rootstack correctly"" This reverts commit bd14603c --- src/otomi-stack.ts | 342 +++++++++++++++++++++++++----- src/services/TeamConfigService.ts | 4 + 2 files changed, 290 insertions(+), 56 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index e297249bc..953282877 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -84,6 +84,7 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' +import { TeamConfigService } from './services/TeamConfigService' interface ExcludedApp extends App { managed: boolean @@ -447,19 +448,9 @@ export default class OtomiStack { settings[settingId] = removeBlankAttributes(updatedSettingsData[settingId] as Record) this.repoService.updateSettings(settings) await this.saveSettings() - await this.doDeployment([ - 'alerts', - 'cluster', - 'dns', - 'ingress', - 'kms', - 'obj', - 'oidc', - 'otomi', - 'platformBackups', - 'smtp', - 'versions', - ]) + await this.doRepoDeployment((repoService) => { + repoService.updateSettings(settings) + }) return settings } @@ -530,7 +521,9 @@ export default class OtomiStack { app = { ...app, ...data } app = this.repoService.updateApp(id, app) await this.saveAdminApp(app) - await this.doDeployment(['apps']) + await this.doRepoDeployment((repoService) => { + repoService.updateApp(id, app) + }) return this.repoService.getApp(id) } @@ -549,7 +542,15 @@ export default class OtomiStack { } }), ) - await this.doDeployment(['apps']) + + await this.doRepoDeployment((repoService) => { + ids.map((id) => { + const orig = repoService.getApp(id) + if (orig && this.canToggleApp(id)) { + repoService.updateApp(id, { enabled }) + } + }) + }) } getTeams(): Array { @@ -590,22 +591,26 @@ export default class OtomiStack { const team = teamConfig.settings const apps = getAppList() const core = this.getCore() - apps.forEach((appId) => { + const teamApps = apps.flatMap((appId) => { const isShared = !!core.adminApps.find((a) => a.name === appId)?.isShared const inTeamApps = !!core.teamApps.find((a) => a.name === appId) - // Admin apps are loaded by loadApps function - if (teamName !== 'admin' && (isShared || inTeamApps)) - this.repoService.getTeamConfigService(teamName).createApp({ id: appId }) + return teamName !== 'admin' && (isShared || inTeamApps) + ? [this.repoService.getTeamConfigService(teamName).createApp({ id: appId })] + : [] // Empty array removes `undefined` entries }) + const policies = getPolicies() if (!data.id) { - const policies = getPolicies() this.repoService.getTeamConfigService(teamName).updatePolicies(policies) } if (deploy) { await this.saveTeam(team) - //TODO do this better for the teamConfig. Now it updates all the team configs of the rootstack - await this.doDeployment(['teamConfig']) + + await this.doRepoDeployment((repoService) => { + repoService.createTeamConfig(teamName, data) + repoService.getTeamConfigService(teamName).setApps(teamApps) + repoService.getTeamConfigService(teamName).updatePolicies(policies) + }) } return team } @@ -613,13 +618,17 @@ export default class OtomiStack { async editTeam(id: string, data: Team): Promise { const team = this.repoService.getTeamConfigService(id).updateSettings(data) await this.saveTeam(team) - await this.doDeployment(['settings'], id) + await this.doTeamDeployment(id, (teamService) => { + teamService.updateSettings(team) + }) return team } async deleteTeam(id: string): Promise { await this.deleteTeamConfig(id) - await this.doDeployment(['teamConfig']) + await this.doRepoDeployment((repoService) => { + repoService.deleteTeamConfig(id) + }) } getTeamServices(teamId: string): Array { @@ -640,7 +649,9 @@ export default class OtomiStack { const backup = this.repoService.getTeamConfigService(teamId).createBackup(data) await this.saveTeamBackup(teamId, data) - await this.doDeployment(['backups'], teamId) + await this.doTeamDeployment(teamId, (teamService) => { + teamService.createBackup(backup) + }) return backup } catch (err) { if (err.code === 409) err.publicMessage = 'Backup name already exists' @@ -656,13 +667,25 @@ export default class OtomiStack { validateBackupFields(data.name, data.ttl) const backup = this.repoService.getTeamConfigService(teamId).updateBackup(name, data) await this.saveTeamBackup(teamId, data) - await this.doDeployment(['backups'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateBackup(name, backup) + }, + false, + ) return backup } async deleteBackup(teamId: string, name: string): Promise { await this.deleteTeamBackup(teamId, name) - await this.doDeployment(['backups'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteBackup(name) + }, + false, + ) } getTeamNetpols(teamId: string): Array { @@ -677,7 +700,13 @@ export default class OtomiStack { try { const netpol = this.repoService.getTeamConfigService(teamId).createNetpol(data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createNetpol(netpol) + }, + false, + ) return netpol } catch (err) { if (err.code === 409) err.publicMessage = 'Network policy name already exists' @@ -693,14 +722,26 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const netpol = this.repoService.getTeamConfigService(teamId).updateNetpol(name, data) await this.saveTeamNetpols(teamId, data) - await this.doDeployment(['netpols'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateNetpol(name, netpol) + }, + false, + ) return netpol } async deleteNetpol(teamId: string, name: string): Promise { const netpol = this.repoService.getTeamConfigService(teamId).getNetpol(name) await this.deleteTeamNetpol(teamId, netpol.name) - await this.doDeployment(['netpols'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteNetpol(name) + }, + false, + ) } getAllUsers(sessionUser: SessionUser): Array { @@ -745,7 +786,9 @@ export default class OtomiStack { } const createdUser = this.repoService.createUser(user) await this.saveUser(createdUser) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.createUser(user) + }) return createdUser } catch (err) { if (err.code === 409) err.publicMessage = 'User email already exists' @@ -758,10 +801,11 @@ export default class OtomiStack { } async editUser(id: string, data: User): Promise { - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const user = this.repoService.updateUser(id, data) await this.saveUser(user) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.updateUser(id, user) + }) return user } @@ -774,7 +818,9 @@ export default class OtomiStack { throw error } await this.deleteUserFile(user) - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + repoService.deleteUser(user.id!) + }) } async editTeamUsers( @@ -786,7 +832,12 @@ export default class OtomiStack { await this.saveUser(updateUser) } const users = this.repoService.getUsers() - await this.doDeployment(['users']) + await this.doRepoDeployment((repoService) => { + for (const user of data) { + const existingUser = repoService.getUser(user.id!) + repoService.updateUser(user.id!, { ...existingUser, teams: user.teams }) + } + }) return users } @@ -817,7 +868,13 @@ export default class OtomiStack { await this.createService(teamId, data.service) } await this.saveTeamProject(teamId, data) - await this.doDeployment(['projects'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createProject(project) + }, + false, + ) return project } catch (err) { if (err.code === 409 && projectNameTaken) err.publicMessage = projectNameTakenPublicMessage @@ -922,6 +979,7 @@ export default class OtomiStack { await this.saveTeamWorkload(teamId, w) await this.saveTeamWorkloadValues(teamId, wv) await this.saveTeamService(teamId, s) + //Leave this for now as we will replace projects await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId, false) return project } @@ -942,7 +1000,17 @@ export default class OtomiStack { await this.deleteTeamService(teamId, p.service.name) } await this.deleteTeamProject(teamId, name) - await this.doDeployment(['projects', 'builds', 'workloads', 'workloadValues', 'services'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteBuild(name) + teamService.deleteWorkload(name) + teamService.deleteWorkloadValues(name) + teamService.deleteService(name) + teamService.deleteProject(name) + }, + false, + ) } getTeamCodeRepos(teamId: string): Array { @@ -960,7 +1028,13 @@ export default class OtomiStack { if (!body.private) unset(body, 'secret') const codeRepo = this.repoService.getTeamConfigService(teamId).createCodeRepo({ ...data, teamId }) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createCodeRepo(codeRepo) + }, + false, + ) return codeRepo } catch (err) { if (err.code === 409) err.publicMessage = 'Code repo label already exists' @@ -978,13 +1052,25 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const codeRepo = this.repoService.getTeamConfigService(teamId).updateCodeRepo(name, body) await this.saveTeamCodeRepo(teamId, codeRepo) - await this.doDeployment(['codeRepos'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateCodeRepo(name, codeRepo) + }, + false, + ) return codeRepo } async deleteCodeRepo(teamId: string, name: string): Promise { await this.deleteTeamCodeRepo(teamId, name) - await this.doDeployment(['codeRepos'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteCodeRepo(name) + }, + false, + ) } async getTestRepoConnect(url: string, teamId: string, secretName: string): Promise { @@ -1057,7 +1143,13 @@ export default class OtomiStack { try { const build = this.repoService.getTeamConfigService(teamId).createBuild({ ...data, teamId }) await this.saveTeamBuild(teamId, data) - await this.doDeployment(['builds'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createBuild(build) + }, + false, + ) return build } catch (err) { if (err.code === 409) err.publicMessage = 'Build name already exists' @@ -1073,7 +1165,13 @@ export default class OtomiStack { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const build = this.repoService.getTeamConfigService(teamId).updateBuild(name, data) await this.saveTeamBuild(teamId, build) - await this.doDeployment(['builds'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateBuild(name, build) + }, + false, + ) return build } @@ -1086,7 +1184,13 @@ export default class OtomiStack { } }) await this.deleteTeamBuild(teamId, name) - await this.doDeployment(['builds'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteBuild(name) + }, + false, + ) } getTeamPolicies(teamId: string): Policies { @@ -1106,7 +1210,14 @@ export default class OtomiStack { teamPolicies[policyId] = removeBlankAttributes(data) const policy = this.getPolicy(teamId, policyId) await this.saveTeamPolicies(teamId) - await this.doDeployment(['policies'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + const rootStackPolicies = teamService.getPolicies() + rootStackPolicies[policyId] = removeBlankAttributes(data) + }, + false, + ) return policy } @@ -1225,7 +1336,14 @@ export default class OtomiStack { .createWorkloadValues({ teamId, values: {}, id: workload.id, name: workload.name }) await this.saveTeamWorkload(teamId, data) await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloads', 'workloadValues'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createWorkload(workload) + teamService.createWorkloadValues(workloadValues) + }, + false, + ) return workload } catch (err) { if (err.code === 409) err.publicMessage = 'Workload name already exists' @@ -1240,7 +1358,13 @@ export default class OtomiStack { async editWorkload(teamId: string, name: string, data: Workload): Promise { const workload = this.repoService.getTeamConfigService(teamId).updateWorkload(name, data) await this.saveTeamWorkload(teamId, workload) - await this.doDeployment(['workloads'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateWorkload(name, workload) + }, + false, + ) return workload } @@ -1254,7 +1378,14 @@ export default class OtomiStack { }) await this.deleteTeamWorkloadValues(teamId, name) await this.deleteTeamWorkload(teamId, name) - await this.doDeployment(['workloads', 'workloadValues'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteWorkloadValues(name) + teamService.deleteWorkload(name) + }, + false, + ) } async editWorkloadValues(teamId: string, name: string, data: WorkloadValues): Promise { @@ -1268,7 +1399,13 @@ export default class OtomiStack { } } await this.saveTeamWorkloadValues(teamId, workloadValues) - await this.doDeployment(['workloadValues'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateWorkloadValues(name, workloadValues) + }, + false, + ) return workloadValues } @@ -1285,7 +1422,13 @@ export default class OtomiStack { try { const service = this.repoService.getTeamConfigService(teamId).createService({ ...data, teamId }) await this.saveTeamService(teamId, data) - await this.doDeployment(['services'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createService(service) + }, + false, + ) return service } catch (err) { if (err.code === 409) err.publicMessage = 'Service name already exists' @@ -1300,7 +1443,13 @@ export default class OtomiStack { async editService(teamId: string, name: string, data: Service): Promise { const service = this.repoService.getTeamConfigService(teamId).updateService(name, data) await this.saveTeamService(teamId, service) - await this.doDeployment(['services'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.updateService(name, service) + }, + false, + ) return service } @@ -1315,7 +1464,13 @@ export default class OtomiStack { }) } await this.deleteTeamService(teamId, name) - await this.doDeployment(['services'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteService(name) + }, + false, + ) } checkPublicUrlInUse(teamId: string, data: any): void { @@ -1369,7 +1524,64 @@ export default class OtomiStack { } } - async doDeployment(collectionIds: string[], teamId?: string, encryptSecrets = true): Promise { + async doTeamDeployment( + teamId: string, + action: (teamService: TeamConfigService) => void, + encryptSecrets = true, + ): Promise { + const rootStack = await getSessionStack() + + try { + // Commit and push Git changes + await this.git.save(this.editor!, encryptSecrets) + + // Execute the provided action dynamically + action(rootStack.repoService.getTeamConfigService(teamId)) + + debug(`Updated root stack values with ${this.sessionId} changes`) + + // Clean up the session + await cleanSession(this.sessionId!) + + // Emit pipeline status + const sha = await rootStack.git.getCommitSha() + this.emitPipelineStatus(sha) + } catch (e) { + if (e instanceof GitPullError) await this.doRestore() + const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } + getIo().emit('db', msg) + throw e + } + } + + async doRepoDeployment(action: (repoService: RepoService) => void, encryptSecrets = true): Promise { + const rootStack = await getSessionStack() + + try { + // Commit and push Git changes + await this.git.save(this.editor!, encryptSecrets) + + // Execute the provided action dynamically + action(rootStack.repoService) + + debug(`Updated root stack values with ${this.sessionId} changes`) + + // Clean up the session + await cleanSession(this.sessionId!) + + // Emit pipeline status + const sha = await rootStack.git.getCommitSha() + this.emitPipelineStatus(sha) + } catch (e) { + if (e instanceof GitPullError) await this.doRestore() + const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } + getIo().emit('db', msg) + throw e + } + } + + //TODO remove this one when we remove projects + async doDeployment(collectionIds?: string[], teamId?: string, encryptSecrets = true): Promise { const rootStack = await getSessionStack() try { // Commit and push Git changes @@ -1542,7 +1754,13 @@ export default class OtomiStack { .createSealedSecret({ ...data, teamId, encryptedData, namespace }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(teamId, sealedSecretChartValues, sealedSecret.name) - await this.doDeployment(['sealedsecrets'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.createSealedSecret(sealedSecret) + }, + false, + ) return sealedSecret } catch (err) { if (err.code === 409) err.publicMessage = 'SealedSecret name already exists' @@ -1566,10 +1784,16 @@ export default class OtomiStack { const encryptedData = Object.assign({}, ...(await Promise.all(encryptedDataPromises))) as EncryptedDataRecord[] const sealedSecret = this.repoService .getTeamConfigService(data.teamId!) - .createSealedSecret({ ...data, encryptedData }) + .updateSealedSecret(name, { ...data, encryptedData }) const sealedSecretChartValues = sealedSecretManifest(data, encryptedData, namespace) await this.saveTeamSealedSecrets(data.teamId!, sealedSecretChartValues, name) - await this.doDeployment(['sealedsecrets'], data.teamId, false) + await this.doTeamDeployment( + data.teamId!, + (teamService) => { + teamService.updateSealedSecret(name, sealedSecret) + }, + false, + ) return sealedSecret } @@ -1578,7 +1802,13 @@ export default class OtomiStack { this.repoService.getTeamConfigService(teamId).deleteSealedSecret(name) const relativePath = getTeamSealedSecretsValuesFilePath(sealedSecret.teamId!, `${name}.yaml`) await this.git.removeFile(relativePath) - await this.doDeployment(['sealedsecrets'], teamId, false) + await this.doTeamDeployment( + teamId, + (teamService) => { + teamService.deleteSealedSecret(name) + }, + false, + ) } async getSealedSecret(teamId: string, name: string): Promise { diff --git a/src/services/TeamConfigService.ts b/src/services/TeamConfigService.ts index 56b2794ac..4b3cf4084 100644 --- a/src/services/TeamConfigService.ts +++ b/src/services/TeamConfigService.ts @@ -434,6 +434,10 @@ export class TeamConfigService { })) } + public setApps(apps: App[]) { + this.teamConfig.apps = apps + } + // ===================================== // == POLICIES CRUD == // ===================================== From 3dd9046dec4624c3e70f7225dc1c8e3606472206 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 4 Mar 2025 11:01:18 +0100 Subject: [PATCH 35/49] fix: the tests --- src/api.authz.test.ts | 2 ++ src/otomi-stack.test.ts | 44 ++++++++++++++++++++++++----------------- src/otomi-stack.ts | 4 ++-- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/api.authz.test.ts b/src/api.authz.test.ts index b68ab3dcc..ff1afefa1 100644 --- a/src/api.authz.test.ts +++ b/src/api.authz.test.ts @@ -47,6 +47,8 @@ describe('API authz tests', () => { otomiStack.saveTeam = jest.fn().mockResolvedValue(undefined) otomiStack.doDeployment = jest.fn().mockImplementation(() => Promise.resolve()) + otomiStack.doRepoDeployment = jest.fn().mockImplementation(() => Promise.resolve()) + otomiStack.doTeamDeployment = jest.fn().mockImplementation(() => Promise.resolve()) await otomiStack.loadValues() await otomiStack.createTeam({ name: 'team1' }) await otomiStack.createTeam({ name: 'team2' }) diff --git a/src/otomi-stack.test.ts b/src/otomi-stack.test.ts index 5c4083425..76ca61a85 100644 --- a/src/otomi-stack.test.ts +++ b/src/otomi-stack.test.ts @@ -66,6 +66,8 @@ describe('Data validation', () => { // Ensure getTeamConfigService() returns our mocked TeamConfigService mockRepoService.getTeamConfigService.mockReturnValue(mockTeamConfigService) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doRepoDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() }) test('should throw exception on duplicated domain', () => { @@ -145,6 +147,8 @@ describe('Work with values', () => { await otomiStack.init() otomiStack.git = new Git('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doRepoDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() }) test('can load from configuration to database and back', async () => { @@ -159,6 +163,8 @@ describe('Workload values', () => { await otomiStack.init() otomiStack.git = new Git('./test', undefined, 'someuser', 'some@ema.il', undefined, undefined) jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doRepoDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() }) test('returns filtered apps if App array is submitted isPreinstalled flag is true', () => { @@ -210,6 +216,8 @@ describe('Users tests', () => { }) jest.spyOn(otomiStack, 'saveUser').mockResolvedValue() jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doRepoDeployment').mockResolvedValue() + jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() jest.spyOn(otomiStack, 'transformApps').mockReturnValue([]) jest.spyOn(otomiStack, 'getApp').mockReturnValue({ id: 'keycloak' }) await otomiStack.initRepo() @@ -336,7 +344,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { name: 'code-1', @@ -357,7 +365,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://gitea.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -389,7 +397,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', @@ -412,7 +420,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://gitea.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -430,12 +438,12 @@ describe('Code repositories tests', () => { jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() @@ -450,7 +458,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { name: 'code-1', @@ -471,7 +479,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://github.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -503,7 +511,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', @@ -526,7 +534,7 @@ describe('Code repositories tests', () => { repositoryUrl: 'https://github.test.com', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -544,12 +552,12 @@ describe('Code repositories tests', () => { jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() @@ -566,7 +574,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.createCodeRepo('demo', { name: 'code-1', @@ -593,7 +601,7 @@ describe('Code repositories tests', () => { secret: 'test', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) createItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -612,7 +620,7 @@ describe('Code repositories tests', () => { } as CodeRepo) const saveTeamCodeReposSpy = jest.spyOn(otomiStack, 'saveTeamCodeRepo').mockResolvedValue() - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() const codeRepo = await otomiStack.editCodeRepo('demo', '1', { teamId: 'demo', @@ -641,7 +649,7 @@ describe('Code repositories tests', () => { secret: 'test', }) expect(saveTeamCodeReposSpy).toHaveBeenCalledWith('demo', codeRepo) - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) updateItemSpy.mockRestore() saveTeamCodeReposSpy.mockRestore() @@ -661,12 +669,12 @@ describe('Code repositories tests', () => { jest.spyOn(otomiStack, 'getCodeRepo').mockReturnValue(codeRepo) const deleteItemSpy = jest.spyOn(teamConfigService, 'deleteCodeRepo').mockResolvedValue({} as never) - const doDeploymentSpy = jest.spyOn(otomiStack, 'doDeployment').mockResolvedValue() + const doDeploymentSpy = jest.spyOn(otomiStack, 'doTeamDeployment').mockResolvedValue() await otomiStack.deleteCodeRepo('demo', '1') expect(deleteItemSpy).toHaveBeenCalledWith('1') - expect(doDeploymentSpy).toHaveBeenCalledWith(['codeRepos'], 'demo', false) + expect(doDeploymentSpy).toHaveBeenCalledWith('demo', expect.any(Function), false) deleteItemSpy.mockRestore() doDeploymentSpy.mockRestore() diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 953282877..62991f24b 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -241,9 +241,9 @@ export default class OtomiStack { const repo = rawRepo as Repo this.repoService = new RepoService(repo) //TODO fix this transforming of the services - this.repoService.getRepo().teamConfig = mapValues(repo.teamConfig, (teamConfig) => ({ + this.repoService.getRepo().teamConfig = mapValues(repo.teamConfig, (teamConfig, teamName) => ({ ...teamConfig, - services: this.transformServices(teamConfig.services, teamConfig.settings.name), + services: this.transformServices(teamConfig.services, teamName), })) } } From f1ed25775d1ca82921f70faf2dd1b76cf4b76425 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Tue, 4 Mar 2025 13:39:34 +0100 Subject: [PATCH 36/49] fix: naming --- src/otomi-stack.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 62991f24b..ab8036d6e 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -110,7 +110,7 @@ const env = cleanEnv({ export const rootPath = '/tmp/otomi/values' //TODO Move this to the repo.ts -const getClusterSettingsFilePath = 'env/settings/cluster.yaml' +const clusterSettingsFilePath = 'env/settings/cluster.yaml' function getTeamSealedSecretsValuesFilePath(teamId: string, sealedSecretsName: string): string { return `env/teams/${teamId}/sealedsecrets/${sealedSecretsName}` } @@ -259,7 +259,7 @@ export default class OtomiStack { this.git = await getRepo(path, url, env.GIT_USER, env.GIT_EMAIL, env.GIT_PASSWORD, branch) await this.git.pull() //TODO fetch this url from the repo - if (await this.git.fileExists(getClusterSettingsFilePath)) break + if (await this.git.fileExists(clusterSettingsFilePath)) break debug(`Values are not present at ${url}:${branch}`) } catch (e) { debug(`${e.message.trim()} for command ${JSON.stringify(e.task?.commands)}`) From 8f6485e3463fefc194be6c941d716d69d0b6af14 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Wed, 5 Mar 2025 10:56:07 +0100 Subject: [PATCH 37/49] feat: add git depth of 5 when pulling --- src/git.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/git.ts b/src/git.ts index 478592476..e2e4cd842 100644 --- a/src/git.ts +++ b/src/git.ts @@ -363,7 +363,7 @@ export class Git { if (!this.url) return debug('Pulling') try { - const summary = await this.git.pull(this.remote, this.branch, { '--rebase': 'true' }) + const summary = await this.git.pull(this.remote, this.branch, { '--rebase': 'true', '--depth': '5' }) const summJson = JSON.stringify(summary) debug(`Pull summary: ${summJson}`) this.commitSha = await this.getCommitSha() From 1ae42dca7c477e1e9bbc04f06e4b1da381f895ce Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Wed, 5 Mar 2025 11:52:16 +0100 Subject: [PATCH 38/49] fix: codeRepo naming and sealedSecrets --- src/repo.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/repo.ts b/src/repo.ts index f1f577771..9a039bad7 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -275,11 +275,11 @@ export function getFileMaps(envDir: string): Array { { kind: 'AplTeamCodeRepo', envDir, - jsonPathExpression: '$.teamConfig.*.coderepos[*]', - pathGlob: `${envDir}/env/teams/*/coderepos/*.yaml`, + jsonPathExpression: '$.teamConfig.*.codeRepos[*]', + pathGlob: `${envDir}/env/teams/*/codeRepos/*.yaml`, processAs: 'arrayItem', resourceGroup: 'team', - resourceDir: 'coderepos', + resourceDir: 'codeRepos', loadToSpec: false, }, { @@ -455,7 +455,16 @@ export async function loadFileToSpec( const data = await deps.loadYaml(filePath) if (fileMap.processAs === 'arrayItem') { const ref: Record[] = get(spec, jsonPath) - ref.push(data?.spec) + //TODO remove this custom workaround for workloadValues as it has no spec + if (fileMap.kind === 'AplTeamWorkloadValues') { + const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] + ref.push({ ...data, name }) + } else if (fileMap.kind === 'AplTeamSecret') { + const name = filePath.match(/\/([^/]+)\.yaml$/)?.[1] + ref.push({ ...data?.spec, name }) + } else { + ref.push(data?.spec) + } } else { const ref: Record = get(spec, jsonPath) // Decrypted secrets may need to be merged with plain text specs From f03792b94fb97350de7856c3ffe761bbcc47cc90 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Wed, 5 Mar 2025 12:04:04 +0100 Subject: [PATCH 39/49] fix: logging --- src/k8s_operations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k8s_operations.ts b/src/k8s_operations.ts index d5747e1b0..3f9bb6630 100644 --- a/src/k8s_operations.ts +++ b/src/k8s_operations.ts @@ -391,7 +391,7 @@ export async function getSecretValues(name: string, namespace: string): Promise< } return decodedData } catch (error) { - // debug(`Failed to get secret values for ${name} in ${namespace}.`) + debug(`Failed to get secret values for ${name} in ${namespace}.`) } } @@ -417,7 +417,7 @@ export async function getSealedSecretSyncedStatus(name: string, namespace: strin } return 'NotFound' } catch (error) { - // debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) + debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) return 'NotFound' } } From c4d426b052a07c9390802b4e51716446f8c3efbd Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Wed, 5 Mar 2025 14:19:50 +0100 Subject: [PATCH 40/49] fix: sealedsecrets loading --- src/repo.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/repo.ts b/src/repo.ts index 9a039bad7..b70996993 100755 --- a/src/repo.ts +++ b/src/repo.ts @@ -504,7 +504,8 @@ export async function loadToSpec( } export async function loadValues(envDir: string, deps = { loadToSpec }): Promise> { - const fileMaps = getFileMaps(envDir).filter((map) => map.loadToSpec === true) + //We need everything to load to spec for the API + const fileMaps = getFileMaps(envDir) const spec = {} await Promise.all( From 0350346846a99f584f8b2467a9a408758b7c9e0f Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:20:35 +0100 Subject: [PATCH 41/49] fix: repoService.ts --- src/services/RepoService.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index 9613d8665..f4c12907e 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -1,3 +1,6 @@ +import { find, has, map, mapValues, merge, remove, set } from 'lodash' +import { v4 as uuidv4 } from 'uuid' +import { AlreadyExists } from '../error' import { Alerts, App, @@ -9,7 +12,6 @@ import { Ingress, Kms, Netpol, - Oidc, Otomi, Policies, Project, @@ -25,9 +27,6 @@ import { Workload, } from '../otomi-models' import { TeamConfigService } from './TeamConfigService' -import { find, has, map, mapValues, merge, remove, set } from 'lodash' -import { v4 as uuidv4 } from 'uuid' -import { AlreadyExists } from '../error' export class RepoService { // We can create an LRU cache if needed with a lot of teams. @@ -42,10 +41,8 @@ export class RepoService { this.repo.ingress ??= {} as Ingress this.repo.kms ??= {} as Kms this.repo.obj ??= {} - this.repo.oidc ??= {} as Oidc this.repo.otomi ??= {} as Otomi this.repo.platformBackups ??= {} - this.repo.smtp ??= {} as Smtp this.repo.users ??= [] this.repo.versions ??= {} as Versions this.repo.teamConfig ??= {} @@ -192,18 +189,25 @@ export class RepoService { } public getSettings(): Settings { - return { + const settings: Settings = { alerts: this.repo.alerts, cluster: this.repo.cluster, dns: this.repo.dns, ingress: this.repo.ingress, kms: this.repo.kms, obj: this.repo.obj, - oidc: this.repo.oidc, otomi: this.repo.otomi, platformBackups: this.repo.platformBackups, - smtp: this.repo.smtp, - } as Settings + } + + if (this.repo.smtp) { + settings.smtp = this.repo.smtp + } + if (this.repo.oidc) { + settings.oidc = this.repo.oidc + } + + return settings } public updateSettings(updates: Partial): void { From c4ed202048a2fb8d725c2fa5108cbc2229d654e8 Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Wed, 5 Mar 2025 15:28:50 +0100 Subject: [PATCH 42/49] feat: remove old secret endpoints and related remainings --- src/api/teams/{teamId}/secrets.ts | 25 ---- src/openapi/api.yaml | 73 ------------ src/openapi/secret.yaml | 110 ------------------ src/otomi-models.ts | 1 - ...epoSerivce.test.ts => RepoService.test.ts} | 0 5 files changed, 209 deletions(-) delete mode 100644 src/api/teams/{teamId}/secrets.ts delete mode 100644 src/openapi/secret.yaml rename src/services/{RepoSerivce.test.ts => RepoService.test.ts} (100%) diff --git a/src/api/teams/{teamId}/secrets.ts b/src/api/teams/{teamId}/secrets.ts deleted file mode 100644 index 1fc5e0b5b..000000000 --- a/src/api/teams/{teamId}/secrets.ts +++ /dev/null @@ -1,25 +0,0 @@ -import Debug from 'debug' -import { Operation, OperationHandlerArray } from 'express-openapi' -import { OpenApiRequestExt } from 'src/otomi-models' - -const debug = Debug('otomi:api:teams:secrets') - -export default function (): OperationHandlerArray { - const get: Operation = [ - ({ otomi, params: { teamId } }: OpenApiRequestExt, res): void => { - debug(`getSecrets(${teamId})`) - res.json('not implemented') - }, - ] - const post: Operation = [ - ({ otomi, params: { teamId }, body }: OpenApiRequestExt, res): void => { - debug(`createSecret(${teamId}, ...)`) - res.json('not implemented') - }, - ] - const api = { - get, - post, - } - return api -} diff --git a/src/openapi/api.yaml b/src/openapi/api.yaml index 7d110279b..7ec8df72d 100644 --- a/src/openapi/api.yaml +++ b/src/openapi/api.yaml @@ -76,23 +76,6 @@ paths: schema: $ref: '#/components/schemas/OtomiCliValues' - /secrets: - get: - operationId: getAllSecrets - description: Get all secrets - x-aclSchema: Secret - responses: - '200': - description: Successfully obtained all secrets - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Secret' - '400': - <<: *BadRequest - /services: get: operationId: getAllServices @@ -432,54 +415,6 @@ paths: <<: *DefaultGetResponses '200': description: Successfully deleted a team sealed secret - '/teams/{teamId}/secrets': - parameters: - - $ref: '#/components/parameters/teamParams' - get: - operationId: getSecrets - description: Get secrets from a given team - x-aclSchema: Secret - responses: - '200': - description: Successfully obtained secrets - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Secret' - '400': - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/OpenApiValidationError' - post: - operationId: createSecret - description: Create a team secret - x-aclSchema: Secret - parameters: - - name: teamId - in: path - description: ID of team - required: true - schema: - type: string - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Secret' - description: Service object - required: true - responses: - <<: *DefaultPostResponses - '200': - description: Successfully stored secret configuration - content: - application/json: - schema: - $ref: '#/components/schemas/Secret' '/netpols': get: @@ -1786,14 +1721,6 @@ components: $ref: sealedsecretskeys.yaml#/SealedSecretsKeys K8sSecret: $ref: k8sSecret.yaml#/K8sSecret - Secret: - $ref: secret.yaml#/Secret - SecretDockerRegistry: - $ref: secret.yaml#/SecretDockerRegistry - SecretGeneric: - $ref: secret.yaml#/SecretGeneric - SecretTLS: - $ref: secret.yaml#/SecretTLS Service: $ref: service.yaml#/Service Session: diff --git a/src/openapi/secret.yaml b/src/openapi/secret.yaml deleted file mode 100644 index 086fda894..000000000 --- a/src/openapi/secret.yaml +++ /dev/null @@ -1,110 +0,0 @@ -Secret: - x-acl: - platformAdmin: - - create-any - - read-any - - update-any - - delete-any - teamAdmin: - - create - - read - - update - - delete - teamMember: - - create - - read - - update - - delete - # additionalProperties: false - properties: - id: - # readOnly: true - type: string - name: - type: string - # A lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com') - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - description: A secret name - namespace: - $ref: definitions.yaml#/idName - description: A Kubernetes namespace. - x-acl: - platformAdmin: - - create-any - - read-any - - update-any - - delete-any - teamAdmin: [] - teamMember: [] - secret: - title: Type - description: Type of secret used in Otomi. - oneOf: - - $ref: '#/SecretGeneric' - - $ref: '#/SecretDockerRegistry' - - $ref: '#/SecretTLS' - type: object - required: - - name - - secret - type: object - x-externalDocsPath: docs/for-devs/console/secrets - -SecretGeneric: - title: Generic - description: The secret's entries listed here must exist in the corresponding vault secret. - properties: - type: - type: string - enum: - - generic - default: generic - entries: - type: array - items: - description: A property in a vault secret. - # a valid k8s secret key must consist of alphanumeric characters, '-', '_' or '.' (e.g. 'key.name', or 'KEY_NAME', or 'key-name' - pattern: ^[-._a-zA-Z0-9]+$ - type: string - uniqueItems: true - minItems: 1 - type: object - required: - - type - - entries - -SecretDockerRegistry: - title: Docker registry - description: A secret in vault with one property ".dockerconfigjson" that is an encoded dockerconfigjson blob. - properties: - type: - type: string - enum: - - docker-registry - default: docker-registry - type: object - required: - - type - -SecretTLS: - title: TLS - description: The TLS fields must exist in the corresponding vault secret. - properties: - type: - type: string - enum: - - tls - default: tls - crt: - type: string - default: tls.crt - key: - type: string - default: tls.key - ca: - type: string - default: ca.crt - type: object - required: - - type - - crt diff --git a/src/otomi-models.ts b/src/otomi-models.ts index d864ca216..ea1c0b91d 100644 --- a/src/otomi-models.ts +++ b/src/otomi-models.ts @@ -9,7 +9,6 @@ export type Backup = components['schemas']['Backup'] export type Kubecfg = components['schemas']['Kubecfg'] export type K8sService = components['schemas']['K8sService'] export type Netpol = components['schemas']['Netpol'] -export type Secret = components['schemas']['Secret'] & { teamId?: string } export type SealedSecret = components['schemas']['SealedSecret'] & { teamId?: string } export type SealedSecretsKeys = components['schemas']['SealedSecretsKeys'] export type K8sSecret = components['schemas']['K8sSecret'] diff --git a/src/services/RepoSerivce.test.ts b/src/services/RepoService.test.ts similarity index 100% rename from src/services/RepoSerivce.test.ts rename to src/services/RepoService.test.ts From 00d26f07dde229139ad644173100a33b0479927f Mon Sep 17 00:00:00 2001 From: Ferruh Cihan <63190600+ferruhcihan@users.noreply.github.com> Date: Wed, 5 Mar 2025 16:24:31 +0100 Subject: [PATCH 43/49] fix: getting statuses --- src/app.ts | 2 +- src/otomi-stack.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app.ts b/src/app.ts index 6ab35b249..704903db1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -99,7 +99,7 @@ const resourceStatus = async (errorSet) => { const promises = resources[resourceType].map(async (resource) => { try { const res = await statusFunctions[resourceType](resource, domainSuffix) - return { [resource.id]: res } + return { [resource.name]: res } } catch (error) { const errorMessage = `${resourceType}-${resource.name}-${error.message}` if (!errorSet.has(errorMessage)) { diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index ab8036d6e..69e81a669 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -72,6 +72,7 @@ import { } from './k8s_operations' import { getFileMaps, loadValues } from './repo' import { RepoService } from './services/RepoService' +import { TeamConfigService } from './services/TeamConfigService' import { validateBackupFields } from './utils/backupUtils' import { getGiteaRepoUrls, @@ -84,7 +85,6 @@ import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './ import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' import { fetchWorkloadCatalog } from './utils/workloadUtils' -import { TeamConfigService } from './services/TeamConfigService' interface ExcludedApp extends App { managed: boolean @@ -1813,7 +1813,7 @@ export default class OtomiStack { async getSealedSecret(teamId: string, name: string): Promise { const sealedSecret = this.repoService.getTeamConfigService(teamId).getSealedSecret(name) - const namespace = sealedSecret.namespace ?? `team-${sealedSecret.teamId}` + const namespace = sealedSecret.namespace ?? `team-${teamId}` const secretValues = (await getSecretValues(sealedSecret.name, namespace)) || {} const isDisabled = isEmpty(secretValues) const encryptedData = Object.entries(sealedSecret.encryptedData).map(([key, value]) => ({ From 071ea976a415c98f142d696507b6aae946c57b4a Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 10 Mar 2025 10:23:53 +0100 Subject: [PATCH 44/49] fix: git save function adjusted git save function to pull only and increase the retries. Also removed the doRestore function after deployment Co-authored-by: Jehoszafat Zimnowoda <17126497+j-zimnowoda@users.noreply.github.com> --- src/git.ts | 21 ++++++++++++++++----- src/k8s_operations.ts | 8 ++++++-- src/otomi-stack.ts | 29 +++++++++++++---------------- src/validators.ts | 6 +++++- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/src/git.ts b/src/git.ts index e2e4cd842..610cf18ea 100644 --- a/src/git.ts +++ b/src/git.ts @@ -10,7 +10,16 @@ import jsonpath from 'jsonpath' import { cloneDeep, get, isEmpty, merge, set, unset } from 'lodash' import { basename, dirname, join } from 'path' import simpleGit, { CheckRepoActions, CleanOptions, CommitResult, ResetMode, SimpleGit } from 'simple-git' -import { cleanEnv, GIT_BRANCH, GIT_LOCAL_PATH, GIT_PASSWORD, GIT_REPO_URL, GIT_USER, TOOLS_HOST } from 'src/validators' +import { + cleanEnv, + GIT_BRANCH, + GIT_LOCAL_PATH, + GIT_PASSWORD, + GIT_PUSH_RETRIES, + GIT_REPO_URL, + GIT_USER, + TOOLS_HOST, +} from 'src/validators' import { parse as parseYaml, stringify as stringifyYaml } from 'yaml' import { BASEURL } from './constants' import { GitPullError, HttpError, ValidationError } from './error' @@ -27,6 +36,7 @@ const env = cleanEnv({ GIT_PASSWORD, GIT_REPO_URL, GIT_USER, + GIT_PUSH_RETRIES, TOOLS_HOST, }) @@ -438,17 +448,18 @@ export class Git { // we are in a unique developer branch, so we can pull, push, and merge // with the remote root, which might have been modified by another developer // since this is a child branch, we don't need to re-init - // retry up to 3 times to pull and push if there are conflicts - const skipInit = true - const retries = 3 + // retry up to 10 times to pull and push if there are conflicts + const retries = env.GIT_PUSH_RETRIES for (let attempt = 1; attempt <= retries; attempt++) { + // await this.pull(skipInit) + await this.git.pull(this.remote, this.branch, { '--rebase': 'true', '--depth': '5' }) try { - await this.pull(skipInit) await this.push() break } catch (error) { if (attempt === retries) throw error debug(`Attempt ${attempt} failed. Retrying...`) + await new Promise((resolve) => setTimeout(resolve, 50)) } } } catch (e) { diff --git a/src/k8s_operations.ts b/src/k8s_operations.ts index 3f9bb6630..630904f0a 100644 --- a/src/k8s_operations.ts +++ b/src/k8s_operations.ts @@ -391,7 +391,9 @@ export async function getSecretValues(name: string, namespace: string): Promise< } return decodedData } catch (error) { - debug(`Failed to get secret values for ${name} in ${namespace}.`) + if (process.env.NODE_ENV !== 'development') { + debug(`Failed to get secret values for ${name} in ${namespace}.`) + } } } @@ -417,7 +419,9 @@ export async function getSealedSecretSyncedStatus(name: string, namespace: strin } return 'NotFound' } catch (error) { - debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) + if (process.env.NODE_ENV !== 'development') { + debug(`Failed to get SealedSecret synced status for ${name} in ${namespace}.`) + } return 'NotFound' } } diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 5d889d50f..23ba9864a 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -3,13 +3,13 @@ import * as k8s from '@kubernetes/client-node' import { V1ObjectReference } from '@kubernetes/client-node' import Debug from 'debug' -import { ObjectStorageKeyRegions, getRegions } from '@linode/api-v4' +import { getRegions, ObjectStorageKeyRegions } from '@linode/api-v4' import { emptyDir, existsSync, pathExists, rmSync, unlink } from 'fs-extra' -import { readFile, readdir, writeFile } from 'fs/promises' +import { readdir, readFile, writeFile } from 'fs/promises' import { generate as generatePassword } from 'generate-password' import { cloneDeep, filter, isArray, isEmpty, map, mapValues, omit, pick, unset } from 'lodash' import { getAppList, getAppSchema, getSpec } from 'src/app' -import { AlreadyExists, GitPullError, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' +import { AlreadyExists, HttpError, OtomiError, PublicUrlExists, ValidationError } from 'src/error' import getRepo, { Git } from 'src/git' import { cleanAllSessions, cleanSession, DbMessage, getIo, getSessionStack } from 'src/middleware' import { @@ -85,7 +85,7 @@ import { getPolicies } from './utils/policiesUtils' import { EncryptedDataRecord, encryptSecretItem, sealedSecretManifest } from './utils/sealedSecretUtils' import { getKeycloakUsers, isValidUsername } from './utils/userUtils' import { ObjectStorageClient } from './utils/wizardUtils' -import { NewChartPayload, fetchWorkloadCatalog, sparseCloneChart } from './utils/workloadUtils' +import { fetchWorkloadCatalog, NewChartPayload, sparseCloneChart } from './utils/workloadUtils' interface ExcludedApp extends App { managed: boolean @@ -1572,17 +1572,16 @@ export default class OtomiStack { debug(`Updated root stack values with ${this.sessionId} changes`) - // Clean up the session - await cleanSession(this.sessionId!) - // Emit pipeline status const sha = await rootStack.git.getCommitSha() this.emitPipelineStatus(sha) } catch (e) { - if (e instanceof GitPullError) await this.doRestore() const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } getIo().emit('db', msg) throw e + } finally { + // Clean up the session + await cleanSession(this.sessionId!) } } @@ -1598,17 +1597,16 @@ export default class OtomiStack { debug(`Updated root stack values with ${this.sessionId} changes`) - // Clean up the session - await cleanSession(this.sessionId!) - // Emit pipeline status const sha = await rootStack.git.getCommitSha() this.emitPipelineStatus(sha) } catch (e) { - if (e instanceof GitPullError) await this.doRestore() const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } getIo().emit('db', msg) throw e + } finally { + // Clean up the session + await cleanSession(this.sessionId!) } } @@ -1638,17 +1636,16 @@ export default class OtomiStack { debug(`Updated root stack values with ${this.sessionId} changes`) - // Clean up the session - await cleanSession(this.sessionId!) - // Emit pipeline status const sha = await rootStack.git.getCommitSha() this.emitPipelineStatus(sha) } catch (e) { - if (e instanceof GitPullError) await this.doRestore() const msg: DbMessage = { editor: 'system', state: 'corrupt', reason: 'deploy' } getIo().emit('db', msg) throw e + } finally { + // Clean up the session + await cleanSession(this.sessionId!) } } diff --git a/src/validators.ts b/src/validators.ts index 7012fa788..ae7b58c85 100644 --- a/src/validators.ts +++ b/src/validators.ts @@ -1,4 +1,4 @@ -import { CleanOptions, CleanedEnvAccessors, ValidatorSpec, bool, cleanEnv as clean, json, num, str } from 'envalid' +import { bool, CleanedEnvAccessors, cleanEnv as clean, CleanOptions, json, num, str, ValidatorSpec } from 'envalid' export const AUTHZ_MOCK_IS_PLATFORM_ADMIN = bool({ desc: 'Indicate if a mocked user is a platform admin', @@ -75,6 +75,10 @@ export const EXPRESS_PAYLOAD_LIMIT = str({ desc: 'The express payload limit', default: '500kb', }) +export const GIT_PUSH_RETRIES = num({ + desc: 'Amount of retries we do to push and pull in the git save function', + default: 10, +}) const { env } = process export function cleanEnv( validators: { [K in keyof T]: ValidatorSpec }, From 75f5d492ac86a15f08fe2d6b360f02fc7f522c54 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 10 Mar 2025 15:54:13 +0100 Subject: [PATCH 45/49] fix: copy values when creating session --- src/middleware/session.ts | 5 +++-- src/otomi-stack.ts | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index 6f930c99b..6e465f273 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -41,8 +41,9 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis if (!sessions[sessionId]) { debug(`Creating session ${sessionId} for user ${editor}`) sessions[sessionId] = new OtomiStack(editor, sessionId) - // init repo without inflating db from files as its slow and we just need a copy of the db - await sessions[sessionId].initGit() + // init repo without inflating values from files as its faster to copy the values + await sessions[sessionId].initGit(false) + sessions[sessionId].repoService = readOnlyStack.repoService } else sessions[sessionId].sessionId = sessionId return sessions[sessionId] } diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index 23ba9864a..af577cb75 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -249,7 +249,7 @@ export default class OtomiStack { } } - async initGit(): Promise { + async initGit(inflateValues = true): Promise { await this.init() // every editor gets their own folder to detect conflicts upon deploy const path = this.getRepoPath() @@ -271,7 +271,9 @@ export default class OtomiStack { await new Promise((resolve) => setTimeout(resolve, timeoutMs)) } - await this.loadValues() + if (inflateValues) { + await this.loadValues() + } debug(`Values are loaded for ${this.editor} in ${this.sessionId}`) } @@ -1434,7 +1436,14 @@ export default class OtomiStack { await this.doTeamDeployment( teamId, (teamService) => { - teamService.updateWorkloadValues(name, workloadValues) + try { + teamService.createWorkloadValues({ ...data, name }) + } catch (error) { + if (error.code === 409) { + debug('Workload values already exists, updating values') + teamService.updateWorkloadValues(name, data) + } + } }, false, ) @@ -2057,7 +2066,7 @@ export default class OtomiStack { } async saveTeamWorkloadValues(teamId: string, workloadValues: WorkloadValues): Promise { - debug(`Saving workload values: ${workloadValues.id!} teamId: ${teamId} name: ${workloadValues.name}`) + debug(`Saving workload values teamId: ${teamId} name: ${workloadValues.name}`) const data = this.getWorkloadValues(teamId, workloadValues.name!) const updatedWorkloadValues = cloneDeep(data) as Record updatedWorkloadValues.values = stringifyYaml(data.values, undefined, 4) From 44c981ed88fcef8464ab0da069662b323e7f037f Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 10 Mar 2025 16:33:12 +0100 Subject: [PATCH 46/49] fix: copy values when creating session --- src/middleware/session.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index 6e465f273..b1d94867b 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -11,6 +11,7 @@ import { OpenApiRequestExt } from 'src/otomi-models' import { default as OtomiStack, rootPath } from 'src/otomi-stack' import { cleanEnv, EDITOR_INACTIVITY_TIMEOUT } from 'src/validators' import { v4 as uuidv4 } from 'uuid' +import cloneDeep from 'lodash/cloneDeep' const debug = Debug('otomi:session') const env = cleanEnv({ @@ -43,7 +44,7 @@ export const setSessionStack = async (editor: string, sessionId: string): Promis sessions[sessionId] = new OtomiStack(editor, sessionId) // init repo without inflating values from files as its faster to copy the values await sessions[sessionId].initGit(false) - sessions[sessionId].repoService = readOnlyStack.repoService + sessions[sessionId].repoService = cloneDeep(readOnlyStack.repoService) } else sessions[sessionId].sessionId = sessionId return sessions[sessionId] } From b2a7af3f87209761426ae14d01ed38cd6f19d909 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Mon, 10 Mar 2025 16:33:54 +0100 Subject: [PATCH 47/49] fix: copy values when creating session --- src/middleware/session.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/session.ts b/src/middleware/session.ts index b1d94867b..bcf4776c5 100644 --- a/src/middleware/session.ts +++ b/src/middleware/session.ts @@ -4,6 +4,7 @@ import { RequestHandler } from 'express' import 'express-async-errors' import { remove } from 'fs-extra' import http from 'http' +import { cloneDeep } from 'lodash' import { join } from 'path' import { Server } from 'socket.io' import { ApiNotReadyError } from 'src/error' @@ -11,7 +12,6 @@ import { OpenApiRequestExt } from 'src/otomi-models' import { default as OtomiStack, rootPath } from 'src/otomi-stack' import { cleanEnv, EDITOR_INACTIVITY_TIMEOUT } from 'src/validators' import { v4 as uuidv4 } from 'uuid' -import cloneDeep from 'lodash/cloneDeep' const debug = Debug('otomi:session') const env = cleanEnv({ From b71d94cadb1f8178f8c8ac642de8fab5c55c6a7b Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 13 Mar 2025 13:39:20 +0100 Subject: [PATCH 48/49] fix: deleting user --- src/otomi-stack.ts | 4 ++-- src/services/RepoService.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/otomi-stack.ts b/src/otomi-stack.ts index af577cb75..6f7a7ecfe 100644 --- a/src/otomi-stack.ts +++ b/src/otomi-stack.ts @@ -822,7 +822,7 @@ export default class OtomiStack { } await this.deleteUserFile(user) await this.doRepoDeployment((repoService) => { - repoService.deleteUser(user.id!) + repoService.deleteUser(user.email) }) } @@ -1921,7 +1921,7 @@ export default class OtomiStack { async deleteUserFile(user: User): Promise { debug(`Deleting user ${user.email}`) - this.repoService.deleteUser(user.id!) + this.repoService.deleteUser(user.email) const users: User[] = [] users.push(user) const fileMap = getFileMaps('').find((fm) => fm.kind === 'AplUser')! diff --git a/src/services/RepoService.ts b/src/services/RepoService.ts index f4c12907e..11ebcb593 100644 --- a/src/services/RepoService.ts +++ b/src/services/RepoService.ts @@ -118,8 +118,8 @@ export class RepoService { return merge(user, updates) } - public deleteUser(id: string): void { - remove(this.repo.users, { id }) + public deleteUser(email: string): void { + remove(this.repo.users, { email }) } private getDefaultTeamConfig(): TeamConfig { From d277db3a0b6e8cd42cc9960a9c68ca1359b1df94 Mon Sep 17 00:00:00 2001 From: Cas Lubbers Date: Thu, 13 Mar 2025 13:53:10 +0100 Subject: [PATCH 49/49] fix: tests --- src/services/RepoService.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/RepoService.test.ts b/src/services/RepoService.test.ts index 66e6f24b1..89f30e566 100644 --- a/src/services/RepoService.test.ts +++ b/src/services/RepoService.test.ts @@ -69,7 +69,7 @@ describe('RepoService', () => { test('should delete a user', () => { const createdUser = service.createUser(user) - service.deleteUser(createdUser.id!) + service.deleteUser(createdUser.email) expect(service.getUsers()).toHaveLength(0) }) })