From be46a81b8ff702f9820ba3374573f40ec6eb133d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 1 Nov 2024 19:15:12 +0100 Subject: [PATCH 001/135] Refactor - Breaking Changes for current DockStat frontend (#18) * Advanced logging * remove docker.io/library from images * updated entrypoint for cup integration * added swagger and more * updated new routes and added first routes which will be controlled by the frontend * Added auth (still buggy here and there) * fixed auth problem when trying to use swagger * Add more logging * added databse functionality * test for auto commit message * test auto git changelog * Update changelog.yml * Update changelog.yml * test new CHANGELOG.md * Changed Files: -------------- .github/workflows/changelog.yml CHANGELOG.md data/database.db package-lock.json package.json * Added offline development capabilities * Advanced frontend customization and new dependecy graph generator using mermaid diagrams and dependecy-cruiser * Dependency cruiser and gitignore adjustmnts * better port assignment, when running in a non-docker environment * Adjust workflows and adjusted entrypoint.sh file * Rate limiter * Rate limiter * adjust workflows --------- Co-authored-by: ItsNik Co-authored-by: root --- .dependency-cruiser.js | 380 +++ .github/workflows/build-dev.yaml | 43 +- .github/workflows/build-image.yml | 44 +- .gitignore | 147 +- Dockerfile | 66 +- LICENSE | 56 +- README.md | 87 +- config/apprise_config_example.yml | 5 - config/db.js | 19 + config/dockerConfig.json | 9 + config/hosts.yaml | 24 - config/loggerConfig.js | 16 + config/swaggerConfig.js | 28 + controllers/containerController.js | 49 + controllers/fetchData.js | 34 + controllers/frontendConfiguration.js | 180 ++ controllers/scheduler.js | 75 + data/database.db | Bin 0 -> 16384 bytes dockstatapi.js | 379 --- entrypoint.sh | 27 +- logger.js | 24 - middleware/authMiddleware.js | 50 + middleware/password.json | 1 + middleware/usePassword.txt | 1 + misc/dependencyGraphs/mermaid-all.txt | 70 + misc/dependencyGraphs/mermaid-api.txt | 33 + misc/dependencyGraphs/mermaid-auth.txt | 8 + misc/dependencyGraphs/mermaid-conf.txt | 26 + misc/dependencyGraphs/mermaid-data.txt | 11 + misc/dependencyGraphs/mermaid-frontend.txt | 11 + misc/entrypoint.sh | 26 + modules/updateAvailable.js | 32 - package-lock.json | 2507 +++++++++++++++++++- package.json | 24 +- routes/auth/routes.js | 145 ++ routes/data/routes.js | 111 + routes/frontendController/routes.js | 340 +++ routes/getter/routes.js | 334 +++ routes/setter/routes.js | 145 ++ scripts/install_apprise.sh | 16 - scripts/notify.sh | 47 - server.js | 33 + swagger/swaggerDocs.js | 10 + utils/containerService.js | 63 + utils/createDependencyGraph.sh | 34 + utils/dockerClient.js | 45 + utils/extractHostData.js | 26 + utils/logger.js | 20 + utils/rateLimiter.js | 8 + utils/writeOfflineLog.js | 31 + 50 files changed, 5150 insertions(+), 750 deletions(-) create mode 100644 .dependency-cruiser.js delete mode 100644 config/apprise_config_example.yml create mode 100644 config/db.js create mode 100644 config/dockerConfig.json delete mode 100644 config/hosts.yaml create mode 100644 config/loggerConfig.js create mode 100644 config/swaggerConfig.js create mode 100644 controllers/containerController.js create mode 100644 controllers/fetchData.js create mode 100644 controllers/frontendConfiguration.js create mode 100644 controllers/scheduler.js create mode 100644 data/database.db delete mode 100644 dockstatapi.js delete mode 100644 logger.js create mode 100644 middleware/authMiddleware.js create mode 100644 middleware/password.json create mode 100644 middleware/usePassword.txt create mode 100644 misc/dependencyGraphs/mermaid-all.txt create mode 100644 misc/dependencyGraphs/mermaid-api.txt create mode 100644 misc/dependencyGraphs/mermaid-auth.txt create mode 100644 misc/dependencyGraphs/mermaid-conf.txt create mode 100644 misc/dependencyGraphs/mermaid-data.txt create mode 100644 misc/dependencyGraphs/mermaid-frontend.txt create mode 100644 misc/entrypoint.sh delete mode 100644 modules/updateAvailable.js create mode 100644 routes/auth/routes.js create mode 100644 routes/data/routes.js create mode 100644 routes/frontendController/routes.js create mode 100644 routes/getter/routes.js create mode 100644 routes/setter/routes.js delete mode 100644 scripts/install_apprise.sh delete mode 100755 scripts/notify.sh create mode 100644 server.js create mode 100644 swagger/swaggerDocs.js create mode 100644 utils/containerService.js create mode 100755 utils/createDependencyGraph.sh create mode 100644 utils/dockerClient.js create mode 100644 utils/extractHostData.js create mode 100644 utils/logger.js create mode 100644 utils/rateLimiter.js create mode 100644 utils/writeOfflineLog.js diff --git a/.dependency-cruiser.js b/.dependency-cruiser.js new file mode 100644 index 00000000..07df12bf --- /dev/null +++ b/.dependency-cruiser.js @@ -0,0 +1,380 @@ +/** @type {import('dependency-cruiser').IConfiguration} */ +module.exports = { + forbidden: [ + { + name: 'no-circular', + severity: 'warn', + comment: + 'This dependency is part of a circular relationship. You might want to revise ' + + 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + from: {}, + to: { + circular: true + } + }, + { + name: 'no-orphans', + comment: + "This is an orphan module - it's likely not used (anymore?). Either use it or " + + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + + "add an exception for it in your dependency-cruiser configuration. By default " + + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", + severity: 'warn', + from: { + orphan: true, + pathNot: [ + '(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files + '[.]d[.]ts$', // TypeScript declaration files + '(^|/)tsconfig[.]json$', // TypeScript config + '(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs + ] + }, + to: {}, + }, + { + name: 'no-deprecated-core', + comment: + 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "bound to exist - node doesn't deprecate lightly.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'core' + ], + path: [ + '^v8/tools/codemap$', + '^v8/tools/consarray$', + '^v8/tools/csvparser$', + '^v8/tools/logreader$', + '^v8/tools/profile_view$', + '^v8/tools/profile$', + '^v8/tools/SourceMap$', + '^v8/tools/splaytree$', + '^v8/tools/tickprocessor-driver$', + '^v8/tools/tickprocessor$', + '^node-inspect/lib/_inspect$', + '^node-inspect/lib/internal/inspect_client$', + '^node-inspect/lib/internal/inspect_repl$', + '^async_hooks$', + '^punycode$', + '^domain$', + '^constants$', + '^sys$', + '^_linklist$', + '^_stream_wrap$' + ], + } + }, + { + name: 'not-to-deprecated', + comment: + 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + + 'version of that module, or find an alternative. Deprecated modules are a security risk.', + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'deprecated' + ] + } + }, + { + name: 'no-non-package-json', + severity: 'error', + comment: + "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + + "available on live with an non-guaranteed version. Fix it by adding the package to the dependencies " + + "in your package.json.", + from: {}, + to: { + dependencyTypes: [ + 'npm-no-pkg', + 'npm-unknown' + ] + } + }, + { + name: 'not-to-unresolvable', + comment: + "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + + 'module: add it to your package.json. In all other cases you likely already know what to do.', + severity: 'error', + from: {}, + to: { + couldNotResolve: true + } + }, + { + name: 'no-duplicate-dep-types', + comment: + "Likely this module depends on an external ('npm') package that occurs more than once " + + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + + "maintenance problems later on.", + severity: 'warn', + from: {}, + to: { + moreThanOneDependencyType: true, + // as it's pretty common to have a type import be a type only import + // _and_ (e.g.) a devDependency - don't consider type-only dependency + // types for this rule + dependencyTypesNot: ["type-only"] + } + }, + + /* rules you might want to tweak for your specific situation: */ + + { + name: 'not-to-spec', + comment: + 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "If there's something in a spec that's of use to other modules, it doesn't have that single " + + 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', + severity: 'error', + from: {}, + to: { + path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' + } + }, + { + name: 'not-to-dev-dep', + severity: 'error', + comment: + "This module depends on an npm package from the 'devDependencies' section of your " + + 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + + 'section of your package.json. If this module is development only - add it to the ' + + 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + from: { + path: '^(\./)', + pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' + }, + to: { + dependencyTypes: [ + 'npm-dev', + ], + // type only dependencies are not a problem as they don't end up in the + // production code or are ignored by the runtime. + dependencyTypesNot: [ + 'type-only' + ], + pathNot: [ + 'node_modules/@types/' + ] + } + }, + { + name: 'optional-deps-used', + severity: 'info', + comment: + "This module depends on an npm package that is declared as an optional dependency " + + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + + "If you're using an optional dependency here by design - add an exception to your" + + "dependency-cruiser configuration.", + from: {}, + to: { + dependencyTypes: [ + 'npm-optional' + ] + } + }, + { + name: 'peer-deps-used', + comment: + "This module depends on an npm package that is declared as a peer dependency " + + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + + "other cases - maybe not so much. If the use of a peer dependency is intentional " + + "add an exception to your dependency-cruiser configuration.", + severity: 'warn', + from: {}, + to: { + dependencyTypes: [ + 'npm-peer' + ] + } + } + ], + options: { + + /* Which modules not to follow further when encountered */ + doNotFollow: { + /* path: an array of regular expressions in strings to match against */ + path: ['node_modules'] + }, + + /* Which modules to exclude */ + // exclude : { + // /* path: an array of regular expressions in strings to match against */ + // path: '', + // }, + + /* Which modules to exclusively include (array of regular expressions in strings) + dependency-cruiser will skip everything not matching this pattern + */ + // includeOnly : [''], + + /* List of module systems to cruise. + When left out dependency-cruiser will fall back to the list of _all_ + module systems it knows of. It's the default because it's the safe option + It might come at a performance penalty, though. + moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] + + As in practice only commonjs ('cjs') and ecmascript modules ('es6') + are widely used, you can limit the moduleSystems to those. + */ + + // moduleSystems: ['cjs', 'es6'], + + /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/' + to open it on your online repo or `vscode://file/${process.cwd()}/` to + open it in visual studio code), + */ + // prefix: `vscode://file/${process.cwd()}/`, + + /* false (the default): ignore dependencies that only exist before typescript-to-javascript compilation + true: also detect dependencies that only exist before typescript-to-javascript compilation + "specify": for each dependency identify whether it only exists before compilation or also after + */ + // tsPreCompilationDeps: false, + + /* list of extensions to scan that aren't javascript or compile-to-javascript. + Empty by default. Only put extensions in here that you want to take into + account that are _not_ parsable. + */ + // extraExtensionsToScan: [".json", ".jpg", ".png", ".svg", ".webp"], + + /* if true combines the package.jsons found from the module up to the base + folder the cruise is initiated from. Useful for how (some) mono-repos + manage dependencies & dependency definitions. + */ + // combinedDependencies: false, + + /* if true leave symlinks untouched, otherwise use the realpath */ + // preserveSymlinks: false, + + /* TypeScript project file ('tsconfig.json') to use for + (1) compilation and + (2) resolution (e.g. with the paths property) + + The (optional) fileName attribute specifies which file to take (relative to + dependency-cruiser's current working directory). When not provided + defaults to './tsconfig.json'. + */ + // tsConfig: { + // fileName: 'tsconfig.json' + // }, + + /* Webpack configuration to use to get resolve options from. + + The (optional) fileName attribute specifies which file to take (relative + to dependency-cruiser's current working directory. When not provided defaults + to './webpack.conf.js'. + + The (optional) `env` and `arguments` attributes contain the parameters + to be passed if your webpack config is a function and takes them (see + webpack documentation for details) + */ + // webpackConfig: { + // fileName: 'webpack.config.js', + // env: {}, + // arguments: {} + // }, + + /* Babel config ('.babelrc', '.babelrc.json', '.babelrc.json5', ...) to use + for compilation + */ + // babelConfig: { + // fileName: '.babelrc', + // }, + + /* List of strings you have in use in addition to cjs/ es6 requires + & imports to declare module dependencies. Use this e.g. if you've + re-declared require, use a require-wrapper or use window.require as + a hack. + */ + // exoticRequireStrings: [], + + /* options to pass on to enhanced-resolve, the package dependency-cruiser + uses to resolve module references to disk. The values below should be + suitable for most situations + + If you use webpack: you can also set these in webpack.conf.js. The set + there will override the ones specified here. + */ + enhancedResolveOptions: { + /* What to consider as an 'exports' field in package.jsons */ + exportsFields: ["exports"], + /* List of conditions to check for in the exports field. + Only works when the 'exportsFields' array is non-empty. + */ + conditionNames: ["import", "require", "node", "default", "types"], + /* + The extensions, by default are the same as the ones dependency-cruiser + can access (run `npx depcruise --info` to see which ones that are in + _your_ environment). If that list is larger than you need you can pass + the extensions you actually use (e.g. [".js", ".jsx"]). This can speed + up module resolution, which is the most expensive step. + */ + // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + /* What to consider a 'main' field in package.json */ + + // if you migrate to ESM (or are in an ESM environment already) you will want to + // have "module" in the list of mainFields, like so: + // mainFields: ["module", "main", "types", "typings"], + mainFields: ["main", "types", "typings"], + /* + A list of alias fields in package.jsons + See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and + the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) + documentation + + Defaults to an empty array (= don't use alias fields). + */ + // aliasFields: ["browser"], + }, + reporterOptions: { + dot: { + /* pattern of modules that can be consolidated in the detailed + graphical dependency graph. The default pattern in this configuration + collapses everything in node_modules to one folder deep so you see + the external modules, but their innards. + */ + collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph.See + https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions + for details and some examples. If you don't specify a theme + dependency-cruiser falls back to a built-in one. + */ + // theme: { + // graph: { + // /* splines: "ortho" gives straight lines, but is slow on big graphs + // splines: "true" gives bezier curves (fast, not as nice as ortho) + // */ + // splines: "true" + // }, + // } + }, + archi: { + /* pattern of modules that can be consolidated in the high level + graphical dependency graph. If you use the high level graphical + dependency graph reporter (`archi`) you probably want to tweak + this collapsePattern to your situation. + */ + collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)', + + /* Options to tweak the appearance of your graph. If you don't specify a + theme for 'archi' dependency-cruiser will use the one specified in the + dot section above and otherwise use the default one. + */ + // theme: { }, + }, + "text": { + "highlightFocused": true + }, + } + } +}; +// generated: dependency-cruiser@16.5.0 on 2024-10-31T20:09:59.974Z diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index 72a370e7..a8d55f2c 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -2,7 +2,8 @@ name: Docker Image CI (dev) on: push: - branches: [ "dev" ] + branches: + - "dev" permissions: packages: write @@ -12,15 +13,35 @@ jobs: build-main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - name: Checkout repository + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - uses: pmorelli92/github-container-registry-build-push@2.2.1 - name: Build and Publish latest service image + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,format=long,prefix= + flavor: | + type=schedule,pattern=nightly + + - name: Build and push + uses: docker/build-push-action@v5 with: - github-push-secret: ${{secrets.GITHUB_TOKEN}} - docker-image-name: dockstatapi - docker-image-tag: dev # optional - dockerfile-path: Dockerfile # optional - build-context: . # optional - build-only: false # optional + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 2836ac9a..8668f9ba 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,8 +1,8 @@ name: Docker Image CI on: - push: - branches: [ "main" ] + release: + types: [published] permissions: packages: write @@ -12,15 +12,35 @@ jobs: build-main: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - name: Checkout repository + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - - uses: pmorelli92/github-container-registry-build-push@2.2.1 - name: Build and Publish latest service image + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,format=long,prefix= + flavor: | + latest=true + + - name: Build and push + uses: docker/build-push-action@v5 with: - github-push-secret: ${{secrets.GITHUB_TOKEN}} - docker-image-name: dockstatapi - docker-image-tag: latest # optional - dockerfile-path: Dockerfile # optional - build-context: . # optional - build-only: false # optional + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 2e7f14aa..f7fcc52b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,143 @@ -dockstat.log -node_modules -.dockerignore -apprise_config.yml \ No newline at end of file +# custom paths: +data/* + +# Created by https://www.toptal.com/developers/gitignore/api/node +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit diff --git a/Dockerfile b/Dockerfile index 8c70ae68..5fc294e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,32 @@ -# Stage 1: Build stage -FROM node:latest AS builder - -LABEL maintainer="https://github.com/its4nik" -LABEL version="1.0" -LABEL description="API for DockStat: Docker container statistics." -LABEL license="MIT" -LABEL repository="https://github.com/its4nik/dockstatapi" -LABEL documentation="https://github.com/its4nik/dockstatapi" - -WORKDIR /api - -COPY package*.json ./ - -RUN npm install --production - -COPY . . - -# Stage 2: Production stage -FROM node:alpine - -WORKDIR /api - -COPY --from=builder /api . - -RUN apk add --no-cache bash curl - -RUN bash /api/scripts/install_apprise.sh - -EXPOSE 7070 - -HEALTHCHECK CMD curl --fail http://localhost:7070/ || exit 1 - -ENTRYPOINT [ "bash", "entrypoint.sh" ] +# Stage 1: Build stage +FROM node:latest AS builder + +LABEL maintainer="https://github.com/its4nik" +LABEL version="2" +LABEL description="API for DockStat" +LABEL license="BSD-3-Clause license " +LABEL repository="https://github.com/its4nik/dockstatapi" +LABEL documentation="https://github.com/its4nik/dockstatapi" + +WORKDIR /api + +COPY package*.json ./ + +RUN npm install --production + +COPY . . + +# Stage 2: Production stage +FROM node:alpine + +WORKDIR /api + +COPY --from=builder /api . + +RUN apk add --no-cache bash curl + +EXPOSE 7070 + +HEALTHCHECK CMD curl --fail http://localhost:7070/api/status || exit 1 + +ENTRYPOINT [ "bash", "misc/entrypoint.sh" ] diff --git a/LICENSE b/LICENSE index 0a731244..1e9ecebd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,28 +1,28 @@ -BSD 3-Clause License - -Copyright (c) 2024, ItsNik - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +BSD 3-Clause License + +Copyright (c) 2024, ItsNik + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 0735e1af..c12afae4 100644 --- a/README.md +++ b/README.md @@ -1,82 +1,13 @@ -# DockstatAPI +# DockStatAPI v2 -## This tool relies on the [DockerSocket Proxy](https://docs.linuxserver.io/images/docker-socket-proxy/), please see it's documentation for more information. +This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. +With this new release a cupple of extra features (compared to v1) are going to be available. -This is the DockStatAPI used in [DockStat](https://github.com/its4nik/dockstat). +### Feature List: -It features an easy way to configure using a yaml file. +- Swagger API Documentation +- "Offline" mode (useful when working on the backend without available test docker sockets) +- Database (Keeps data for 24 hours max) +- Advanced authentication using hashes and salt -You can specify multiple hosts, using a Docker Socket Proxy like this: - -## Installation: - -docker-compose.yaml -```yaml -services: - dockstatapi: - image: ghcr.io/its4nik/dockstatapi:latest - container_name: dockstatapi - environment: - - SECRET=CHANGEME # This is required in the header 'Authorization': 'CHANGEME' - - ALLOW_LOCALHOST="False" # Defaults to False - ports: - - "7070:7070" - volumes: - - ./dockstatapi:/api/config # Place your hosts.yaml file here - restart: always -``` - -Example docker-socket onfiguration: - -```yaml -socket-proxy: - image: lscr.io/linuxserver/socket-proxy:latest - container_name: socket-proxy - environment: - - CONTAINERS=1 # Needed for the api to worrk - - INFO=1 # Needed for the api to work - volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro - restart: unless-stopped - read_only: true - tmpfs: - - /run - ports: - - 2375:2375 -``` - -Configuration inside the mounted folder, as hosts.yaml -```yaml -mintimeout: 10000 # The minimum time to wait before querying the same server again, defaults to 5000 Ms - -log: - logsize: 10 # Specify the Size of the log files in MB, default is 1MB - LogCount: 1 # How many log files should be kept in rotation. Default is 5 - -hosts: - YourHost1: - url: hetzner - port: 2375 - -# This is used for DockStat -# Please see the dockstat documentation for more information -tags: - raspberry: red-200 - private: violet-400 - -container: - dozzle: # Container name - link: https://github.com -``` - -Please see the documentation for more information on what endpoints will be provieded. - -[Documentation](https://outline.itsnik.de/s/dockstat/doc/backend-api-reference-YzcBbDvY33) - ---- - -This Api uses a "queuing" mechanism to communicate to the servers, so that we dont ask the same server multiple times without getting an answer. - -Feel free to use this API in any of your projects :D - -The `/stats` endpoint server all information that are gethered from the server in a json format. +# 🔗 DockStatAPI v2 Documentation diff --git a/config/apprise_config_example.yml b/config/apprise_config_example.yml deleted file mode 100644 index 88e33870..00000000 --- a/config/apprise_config_example.yml +++ /dev/null @@ -1,5 +0,0 @@ -# Please see the apprise documentation -urls: - - tgram://bottoken/ChatID - - rocket://user:password@hostname/RoomID/Channel - - ntfy://topic/ diff --git a/config/db.js b/config/db.js new file mode 100644 index 00000000..9317ab40 --- /dev/null +++ b/config/db.js @@ -0,0 +1,19 @@ +const sqlite3 = require('sqlite3').verbose(); +const logger = require('./../utils/logger'); +const path = require('path'); +const dbPath = path.join(__dirname, '../data/database.db'); + +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + logger.error('Error opening database:', err.message); + } else { + db.run(`CREATE TABLE IF NOT EXISTS data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + info TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + )`); + logger.info('Database created / opened succesfully'); + } +}); + +module.exports = db; \ No newline at end of file diff --git a/config/dockerConfig.json b/config/dockerConfig.json new file mode 100644 index 00000000..9ec4caf3 --- /dev/null +++ b/config/dockerConfig.json @@ -0,0 +1,9 @@ +{ + "hosts": [ + { + "name": "Fin-2", + "url": "100.89.35.135", + "port": "2375" + } + ] +} diff --git a/config/hosts.yaml b/config/hosts.yaml deleted file mode 100644 index d40e6697..00000000 --- a/config/hosts.yaml +++ /dev/null @@ -1,24 +0,0 @@ -mintimeout: 10000 # The minimum time to wait before querying the same server again, defaults to 5000 Ms - -log: - logsize: 10 # Specify the Size of the log files in MB, default is 1MB - LogCount: 1 # How many log files should be kept in rotation. Default is 5 - -tags: - raspberry: red-200 - private: violet-400 - -hosts: - YourHost1: - url: hetzner - port: 2375 - - YourHost2: - url: 100.78.180.21 - port: 2375 - -container: - dozzle: # Container name - link: https://github.com - icon: minecraft.png - tags: private,raspberry diff --git a/config/loggerConfig.js b/config/loggerConfig.js new file mode 100644 index 00000000..79503488 --- /dev/null +++ b/config/loggerConfig.js @@ -0,0 +1,16 @@ +const { format } = require('winston'); + +module.exports = { + level: 'info', + format: format.combine( + format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), + format.printf(({ timestamp, level, message }) => `${timestamp} [${level.toUpperCase()}]: ${message}`) + ), + transports: { + console: true, + file: { + enabled: true, + filename: 'logs/app.log', + }, + }, +}; diff --git a/config/swaggerConfig.js b/config/swaggerConfig.js new file mode 100644 index 00000000..c79ae476 --- /dev/null +++ b/config/swaggerConfig.js @@ -0,0 +1,28 @@ +const options = { + definition: { + openapi: '3.0.0', + info: { + title: 'Your API Documentation', + version: '1.0.0', + description: 'API documentation with authentication', + }, + components: { + securitySchemes: { + passwordAuth: { + type: 'apiKey', + in: 'header', + name: 'x-password', + description: 'Password required for authentication', + }, + }, + }, + security: [ + { + passwordAuth: [], + }, + ], + }, + apis: ['./routes/*/*.js'], // Point to your route files +}; + +module.exports = options; diff --git a/controllers/containerController.js b/controllers/containerController.js new file mode 100644 index 00000000..f62ec5ce --- /dev/null +++ b/controllers/containerController.js @@ -0,0 +1,49 @@ +const fs = require("fs"); +const path = require("path"); +const { getDockerClient } = require("../utils/dockerClient"); +const logger = require("../utils/logger"); + +const getContainers = async (req, res) => { + const host = req.query.host || "local"; + logger.info(`Fetching containers from host: ${host}`); + try { + const docker = getDockerClient(host); + const containers = await docker.listContainers(); + + res.status(200).json(containers); + } catch (err) { + logger.error( + `Error fetching containers from host: ${host} - ${err.message || "Unknown error"} - Full error: ${JSON.stringify(err, null, 2)}`, + ); + res.status(500).json({ + error: `Error fetching containers: ${err.message || "Unknown error"}`, + }); + } +}; + +const getContainerStats = async (containerID, containerHost) => { + logger.info( + `Fetching stats for container: ${containerID} from host: ${containerHost}`, + ); + try { + const docker = getDockerClient(containerHost); + const container = docker.getContainer(containerID); + const stats = await container.stats({ stream: false }); + logger.info( + `Successfully fetched stats for container: ${containerID} from host: ${containerHost}`, + ); + res.status(200).json(stats); + } catch (error) { + logger.error( + `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${error.message}`, + ); + res + .status(500) + .json({ error: `Error fetching container stats: ${error.message}` }); + } +}; + +module.exports = { + getContainers, + getContainerStats, +}; diff --git a/controllers/fetchData.js b/controllers/fetchData.js new file mode 100644 index 00000000..43b4f8a1 --- /dev/null +++ b/controllers/fetchData.js @@ -0,0 +1,34 @@ +const db = require("../config/db"); +const { fetchAllContainers } = require("../utils/containerService"); +const logger = require("./../utils/logger"); +const path = require("path"); +const fs = require("fs"); + +const fetchData = async () => { + try { + const allContainerData = await fetchAllContainers(); + const data = allContainerData; + + if (process.env.OFFLINE === "true") { + logger.info("No new data inserted --- OFFLINE MODE"); + } else { + // Insert data into the SQLite database + db.run( + `INSERT INTO data (info) VALUES (?)`, + [JSON.stringify(data)], + function (error) { + if (error) { + logger.info("Error inserting data:", error.message); + console.error("Error inserting data:", error.message); + return; + } + logger.info(`Data inserted with ID: ${this.lastID}`); + }, + ); + } + } catch (error) { + logger.error("Error fetching data:", error.message); + } +}; + +module.exports = fetchData; diff --git a/controllers/frontendConfiguration.js b/controllers/frontendConfiguration.js new file mode 100644 index 00000000..ff1ce3ea --- /dev/null +++ b/controllers/frontendConfiguration.js @@ -0,0 +1,180 @@ +const fs = require("fs"); +const path = require("path"); +const dataPath = path.join(__dirname, "../data/frontendConfiguration.json"); +const logger = require("../utils/logger"); + +async function hideContainer(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + data[containerIndex].hidden = true; + await saveData(data); + } else { + data.push({ name: containerName, hidden: true }); + await saveData(data); + } + } catch (error) { + logger.error(error); + } +} + +async function unhideContainer(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + delete data[containerIndex].hidden; + await saveData(data); + cleanupData(); + } + } catch (error) { + logger.error(error); + } +} + +async function addTagToContainer(containerName, tag) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + if (!data[containerIndex].tags) { + data[containerIndex].tags = []; + } + data[containerIndex].tags.push(tag); + await saveData(data); + } else { + data.push({ name: containerName, tags: [tag] }); + await saveData(data); + } + } catch (error) { + logger.error(error); + } +} + +async function removeTagFromContainer(containerName, tag) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1 && data[containerIndex].tags) { + data[containerIndex].tags = data[containerIndex].tags.filter( + (t) => t !== tag, + ); + await saveData(data); + cleanupData(); + } + } catch (error) { + logger.error(error); + } +} + +async function pinContainer(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + data[containerIndex].pinned = true; + await saveData(data); + } else { + data.push({ name: containerName, pinned: true }); + await saveData(data); + } + } catch (error) { + logger.error(error); + } +} + +async function unpinContainer(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + delete data[containerIndex].pinned; + await saveData(data); + cleanupData(); + } + } catch (error) { + logger.error(error); + } +} + +async function readData() { + try { + const data = await fs.promises.readFile(dataPath, "utf-8"); + return JSON.parse(data); + } catch (error) { + console.error("readData"); + if (error.code === "ENOENT") { + await saveData([]); + return []; + } else { + throw error; + } + } +} + +async function saveData(data) { + try { + await fs.promises.writeFile( + dataPath, + JSON.stringify(data, null, 2), + "utf-8", + ); + logger.info("Succesfully wrote to file"); + } catch (error) { + logger.error(error); + } +} + +async function cleanupData() { + try { + const data = await readData(); + let cleanedData = []; + + if (data && Array.isArray(data)) { + cleanedData = data.filter((container) => { + // Filter out containers with empty "tags" or containers with only one property (name) + if ( + container.tags && + Array.isArray(container.tags) && + container.tags.length === 0 + ) { + delete container.tags; + } + return Object.keys(container).length > 1; + }); + } + + await saveData(cleanedData); + } catch (error) { + logger.error(error); + } +} + +module.exports = { + hideContainer, + unhideContainer, + addTagToContainer, + removeTagFromContainer, + pinContainer, + unpinContainer, + cleanupData, +}; diff --git a/controllers/scheduler.js b/controllers/scheduler.js new file mode 100644 index 00000000..5bb3ca7b --- /dev/null +++ b/controllers/scheduler.js @@ -0,0 +1,75 @@ +const fetchData = require("./fetchData"); +const logger = require("../utils/logger"); +const db = require("../config/db"); + +let fetchInterval = 5 * 60 * 1000; +let intervalId; + +const scheduleFetch = () => { + fetchData().then(() => { + cleanupOldEntries(); + }); + intervalId = setInterval(() => { + logger.info( + `Fetching data at interval of ${fetchInterval / 1000} seconds.`, + ); + cleanupOldEntries(); + fetchData(); + }, fetchInterval); + logger.info(`Data fetching scheduled every ${fetchInterval / 1000} seconds.`); +}; + +const setFetchInterval = (newInterval) => { + if (intervalId) { + clearInterval(intervalId); + logger.info(`Cleared existing fetch interval.`); + } + fetchInterval = newInterval; + scheduleFetch(); + logger.info(`Fetch interval updated to ${fetchInterval / 1000} seconds.`); +}; + +const parseInterval = (interval) => { + const timeUnits = { + s: 1000, + m: 60 * 1000, + h: 60 * 60 * 1000, + }; + + let totalMilliseconds = 0; + const regex = /(\d+)([smh])/g; + let match; + + while ((match = regex.exec(interval))) { + const value = parseInt(match[1], 10); + const unit = match[2]; + totalMilliseconds += value * timeUnits[unit]; + } + + return totalMilliseconds; +}; + +const getCurrentSchedule = () => { + return { + interval: fetchInterval / 1000, + }; +}; + +const cleanupOldEntries = async () => { + const twentyFourHoursAgo = new Date( + Date.now() - 24 * 60 * 60 * 1000, + ).toISOString(); + try { + await db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo); + logger.info(`Old entries cleared from the database.`); + } catch (error) { + logger.error(`Error clearing old entries: ${error.message}`); + } +}; + +module.exports = { + scheduleFetch, + setFetchInterval, + parseInterval, + getCurrentSchedule, +}; diff --git a/data/database.db b/data/database.db new file mode 100644 index 0000000000000000000000000000000000000000..80295980fc1caba1cff16d304197ec55893652f2 GIT binary patch literal 16384 zcmeI2-ELdQ6@?`ytyh63@AMkC zVd_0QK0W;1;Yshs$+M$_lUKdpAHM1xygdE<*)b*`9Uh-keRg!%`}FX$gO|@wd!M{KIl=YL>cKBg4~|~kUigFh-F)|P z+`(^m>j-oNIszSmjzCA?zenJ2zuEoS&fjFfSeVTs*5o8cM$jMvj?9?#C& z1DZz{12N#+$@J@^NxF;DS<1yL zWnLK1wUe5-6DU!{}kDB z;5xM13EQJvQq$7wz>3ulY+@|j7f)G=l5HR>WiUC~XqnTg=&Z>U%QEMQWkiK$VvB%l zQ&XBdC(YvEJ=6pv=V(O@A$SL}<*0RJH>0=V_5Jr3c}Yuol{rgZNA3+pW${DSvqVNn zS3(ISlR+j*A}xdhP7{K&DPr zbI+QZ5*$pMv}!UpM>o&4XKhaN61C}kny;2TwNw-x2vRDjV*-h&3sZzj7m>448dNC7}v}=9huOzY;Iic48w;rkf>>nX=7L$sL~E;Z0!x@d%_Xq72z{k1tdzc zcK9^zzbLD9pfZ%7DRs(BJ!_S|LQ6 zoN7QeMmJB@cx=piV}{4)!}04E)5+`aj{F#_;B*;~D1E%>kP?p;3yJj^tA))zdm*EX z9IrTQn_>`-nZhk&UMg$7%LM#6Y{63_2Am#XS;vZMEy)&6h2@J7>EgkFBx96eR+&bP z#MJ}|VNf8j#d&-F{r}y6?%=n(bp$#B9f6KON1!9n5$FhX1Udp8fsQ~&pd;}AMd0!7 zTTgyb+Y;*A`iZpC)D70Pm4<#$h5k)t`l|CGqQQSJ!5lOOR;~8fV&oth-g|P&*O>iuOVOiAR^h! zZQ)74`&1EfzsTEICWv^FoCYxCIN@QToi2jaFhr0x52w8lww4eEKWD`k;R=_}O1dNa zTNBptHYaXQci{&tqgJI%(S!)NE~2_=Pa$w0fiUX~56lpEsfxiim|}n)(sghy_>fHA z&WADpDcwN9MdFV5g5LrvtPl%9th z8BymGj~rdWbjlZnj2UFMu- z=-^x?bb*JYl|=AGq^wKDN&)9MA;X`@0Sz{wzMy(IcM`gTmm(Hl5B^pDVjf$a2dYj?wNydXS5)NcZ3jG+2MZ>nhs^&4t2DS5~ z#$=l+!E&G1z6V-F;Ct7}hBy3W^aa80MX{H-W|QDNLK_pB=W3X?CT@;*HoeYqG8*~u z?G_Zyd@81(gt3CHhU5vwU&8v$NtGlSiG>9DPLz)v-Bt}J+n1RKJZML9`inv&B zWc`E?XAirgycS$6z73EP4OZAYNo-e}s47Nk3~!#R7#q_zhjO$EC_Sn`q*`n^=qg1kqQ4FXWWc#hdfhZ2e!$_RZY=mv|}u2Mn(ZxNqujs}H< ztbUkdEfKQWw1g!^NbCgesFt^5*9kV}Y))`IE8i`-F;qkoYipcEcN&9TkClg`he`?N zGK&(cC~4*tJbR0F1i@5bpoXl*BE`2Umu+qwEfDxl@Gv)n78YabqKtPu|MqB5j+`=g g=J0atI=I!GH|6%_kAexc&-p&%J!YRWfv>Iq0`|MTqyPW_ literal 0 HcmV?d00001 diff --git a/dockstatapi.js b/dockstatapi.js deleted file mode 100644 index d06fb1e7..00000000 --- a/dockstatapi.js +++ /dev/null @@ -1,379 +0,0 @@ -const express = require('express'); -const path = require('path'); -const yaml = require('yamljs'); -const Docker = require('dockerode'); -const cors = require('cors'); -const fs = require('fs'); -const { exec } = require('child_process'); -const logger = require('./logger'); -const updateAvailable = require('./modules/updateAvailable') -const app = express(); -const port = 7070; -const key = process.env.SECRET || 'CHANGE-ME'; -const skipAuth = process.env.SKIP_AUTH || 'True' -const cupUrl = process.env.CUP_URL || 'null' - -let config = yaml.load('./config/hosts.yaml'); -let hosts = config.hosts; -let containerConfigs = config.container || {}; -let maxlogsize = config.log.logsize || 1; -let LogAmount = config.log.LogCount || 5; -let queryInterval = config.mintimeout || 5000; -let latestStats = {}; -let hostQueues = {}; -let previousNetworkStats = {}; -let generalStats = {}; -let previousContainerStates = {}; -let previousRunningContainers = {}; - - -app.use(cors()); -app.use(express.json()); - -const authenticateHeader = (req, res, next) => { - const authHeader = req.headers['authorization']; - - if (skipAuth === 'True') { - next(); - } else { - if (!authHeader || authHeader !== key) { - logger.error(`${authHeader} != ${key}`); - return res.status(401).json({ error: "Unauthorized" }); - } - else { - next(); - } - } -}; - -function createDockerClient(hostConfig) { - return new Docker({ - host: hostConfig.url, - port: hostConfig.port, - }); -} - -function getTagColor(tag) { - const tagsConfig = config.tags || {}; - return tagsConfig[tag] || ''; -} - -async function getContainerStats(docker, containerId) { - const container = docker.getContainer(containerId); - return new Promise((resolve, reject) => { - container.stats({ stream: false }, (err, stats) => { - if (err) return reject(err); - resolve(stats); - }); - }); -} - -async function handleContainerStateChanges(hostName, currentContainers) { - const currentRunningContainers = currentContainers - .filter(container => container.state === 'running') - .reduce((map, container) => { - map[container.id] = container; - return map; - }, {}); - - const previousHostContainers = previousRunningContainers[hostName] || {}; - - // Check for containers that have been removed or exited - for (const containerId of Object.keys(previousHostContainers)) { - const container = previousHostContainers[containerId]; - if (!currentRunningContainers[containerId]) { - if (container.state === 'running') { - // Container removed - exec(`bash ./scripts/notify.sh REMOVE ${containerId} ${container.name} ${hostName} ${container.state}`, (error, stdout, stderr) => { - if (error) { - logger.error(`Error executing REMOVE notify.sh: ${error.message}`); - } else { - logger.info(`Container removed: ${container.name} (${containerId}) from host ${hostName}`); - logger.info(stdout); - } - }); - } - else if (container.state === 'exited') { - // Container exited - exec(`bash ./scripts/notify.sh EXIT ${containerId} ${container.name} ${hostName} ${container.state}`, (error, stdout, stderr) => { - if (error) { - logger.error(`Error executing EXIT notify.sh: ${error.message}`); - } else { - logger.info(`Container exited: ${container.name} (${containerId}) from host ${hostName}`); - logger.info(stdout); - } - }); - } - } - } - - // Check for new containers or state changes - for (const containerId of Object.keys(currentRunningContainers)) { - const container = currentRunningContainers[containerId]; - const previousContainer = previousHostContainers[containerId]; - - if (!previousContainer) { - // New container added - exec(`bash ./scripts/notify.sh ADD ${containerId} ${container.name} ${hostName} ${container.state}`, (error, stdout, stderr) => { - if (error) { - logger.error(`Error executing ADD notify.sh: ${error.message}`); - } else { - logger.info(`Container added: ${container.name} (${containerId}) to host ${hostName}`); - logger.info(stdout); - } - }); - } else if (previousContainer.state !== container.state) { - // Container state has changed - const newState = container.state; - if (newState === 'exited') { - exec(`bash ./scripts/notify.sh EXIT ${containerId} ${container.name} ${hostName} ${newState}`, (error, stdout, stderr) => { - if (error) { - logger.error(`Error executing EXIT notify.sh: ${error.message}`); - } else { - logger.info(`Container exited: ${container.name} (${containerId}) from host ${hostName}`); - logger.info(stdout); - } - }); - } else { - // Any other state change - exec(`bash ./scripts/notify.sh ANY ${containerId} ${container.name} ${hostName} ${newState}`, (error, stdout, stderr) => { - if (error) { - logger.error(`Error executing ANY notify.sh: ${error.message}`); - } else { - logger.info(`Container state changed to ${newState}: ${container.name} (${containerId}) from host ${hostName}`); - logger.info(stdout); - } - }); - } - } - } - - // Update the previous state for the next comparison - previousRunningContainers[hostName] = currentRunningContainers; -} - -async function queryHostStats(hostName, hostConfig) { - logger.debug(`Querying Docker stats for host: ${hostName} (${hostConfig.url}:${hostConfig.port})`); - - const docker = createDockerClient(hostConfig); - - try { - const info = await docker.info(); - const totalMemory = info.MemTotal; - const totalCPUs = info.NCPU; - const containers = await docker.listContainers({ all: true }); - - const statsPromises = containers.map(async (container) => { - try { - const containerName = container.Names[0].replace('/', ''); - const containerState = container.State; - const updateAvailableFlag = await updateAvailable(container.Image, cupUrl); - let networkMode = container.HostConfig.NetworkMode; - - // Check if network mode is in the format "container:IDXXXXXXXX" - if (networkMode.startsWith("container:")) { - const linkedContainerId = networkMode.split(":")[1]; - const linkedContainer = await docker.getContainer(linkedContainerId).inspect(); - const linkedContainerName = linkedContainer.Name.replace('/', ''); // Remove leading slash - - networkMode = `Container: ${linkedContainerName}`; // Format the network mode - } - - if (containerState !== 'running') { - previousContainerStates[container.Id] = containerState; - return { - name: containerName, - id: container.Id, - hostName: hostName, - state: containerState, - image: container.Image, - update_available: updateAvailableFlag || false, - cpu_usage: 0, - mem_usage: 0, - mem_limit: 0, - net_rx: 0, - net_tx: 0, - current_net_rx: 0, - current_net_tx: 0, - networkMode: networkMode, - link: containerConfigs[containerName]?.link || '', - icon: containerConfigs[containerName]?.icon || '', - tags: getTagColor(containerConfigs[containerName]?.tags || ''), - }; - } - - // Fetch container stats for running containers - const containerStats = await getContainerStats(docker, container.Id); - const containerCpuUsage = containerStats.cpu_stats.cpu_usage.total_usage; - const containerMemoryUsage = containerStats.memory_stats.usage; - - let netRx = 0, netTx = 0, currentNetRx = 0, currentNetTx = 0; - - if (networkMode !== 'host' && containerStats.networks?.eth0) { - const previousStats = previousNetworkStats[container.Id] || { rx_bytes: 0, tx_bytes: 0 }; - currentNetRx = containerStats.networks.eth0.rx_bytes - previousStats.rx_bytes; - currentNetTx = containerStats.networks.eth0.tx_bytes - previousStats.tx_bytes; - - previousNetworkStats[container.Id] = { - rx_bytes: containerStats.networks.eth0.rx_bytes, - tx_bytes: containerStats.networks.eth0.tx_bytes, - }; - - netRx = containerStats.networks.eth0.rx_bytes; - netTx = containerStats.networks.eth0.tx_bytes; - } - - previousContainerStates[container.Id] = containerState; - const config = containerConfigs[containerName] || {}; - - const tagArray = (config.tags || '') - .split(',') - .map(tag => { - const color = getTagColor(tag); - return color ? `${tag}:${color}` : tag; - }) - .join(','); - - return { - name: containerName, - id: container.Id, - hostName: hostName, - image: container.Image, - update_available: updateAvailableFlag || false, - state: containerState, - cpu_usage: containerCpuUsage, - mem_usage: containerMemoryUsage, - mem_limit: containerStats.memory_stats.limit, - net_rx: netRx, - net_tx: netTx, - current_net_rx: currentNetRx, - current_net_tx: currentNetTx, - networkMode: networkMode, - link: config.link || '', - icon: config.icon || '', - tags: tagArray, - }; - } catch (err) { - logger.error(`Failed to fetch stats for container ${container.Names[0]} (${container.Id}): ${err.message}`); - return null; - } - }); - - const hostStats = await Promise.all(statsPromises); - const validStats = hostStats.filter(stat => stat !== null); - - const totalCpuUsage = validStats.reduce((acc, container) => acc + parseFloat(container.cpu_usage), 0); - const totalMemoryUsage = validStats.reduce((acc, container) => acc + container.mem_usage, 0); - const memoryUsagePercent = ((totalMemoryUsage / totalMemory) * 100).toFixed(2); - - generalStats[hostName] = { - containerCount: validStats.length, - totalCPUs: totalCPUs, - totalMemory: totalMemory, - cpuUsage: totalCpuUsage, - memoryUsage: memoryUsagePercent, - }; - - latestStats[hostName] = validStats; - - logger.debug(`Fetched stats for ${validStats.length} containers from ${hostName}`); - - // Handle container state changes - await handleContainerStateChanges(hostName, validStats); - } catch (err) { - logger.error(`Failed to fetch containers from ${hostName}: ${err.message}`); - } -} - - -async function handleHostQueue(hostName, hostConfig) { - while (true) { - await queryHostStats(hostName, hostConfig); - await new Promise(resolve => setTimeout(resolve, queryInterval)); - } -} - -// Initialize the host queues -function initializeHostQueues() { - for (const [hostName, hostConfig] of Object.entries(hosts)) { - hostQueues[hostName] = handleHostQueue(hostName, hostConfig); - } -} - -// Dynamically reloads the yaml file -function reloadConfig() { - for (const hostName in hostQueues) { - hostQueues[hostName] = null; - } - try { - config = yaml.load('./config/hosts.yaml'); - hosts = config.hosts; - containerConfigs = config.container || {}; - maxlogsize = config.log.logsize || 1; - LogAmount = config.log.LogCount || 5; - queryInterval = config.mintimeout || 5000; - - logger.info('Configuration reloaded successfully.'); - - initializeHostQueues(); - } catch (err) { - logger.error(`Failed to reload configuration: ${err.message}`); - } -} - -// Watch the YAML file for changes and reload the config -fs.watchFile('./config/hosts.yaml', (curr, prev) => { - if (curr.mtime !== prev.mtime) { - logger.info('Detected change in configuration file. Reloading...'); - reloadConfig(); - } -}); - -// Endpoint to get stats -app.get('/stats', authenticateHeader, (req, res) => { - res.json(latestStats); -}); - -// Endpoint for general Host based statistics -app.get('/hosts', authenticateHeader, (req, res) => { - res.json(generalStats); -}); - -// Read Only config endpoint -app.get('/config', authenticateHeader, (req, res) => { - const filePath = path.join(__dirname, './config/hosts.yaml'); - res.set('Content-Type', 'text/plain'); // Keep as plain text - fs.readFile(filePath, 'utf8', (err, data) => { - logger.debug('Requested config file: ' + filePath); - if (err) { - logger.error(err); - res.status(500).send('Error reading file'); - } else { - res.send(data); - } - }); -}); - -app.get('/', (req, res) => { - res.redirect(301, '/stats'); -}); - -app.get('/status', (req, res) => { - logger.info("Healthcheck requested"); - return res.status(200).send('UP'); -}); - -// Start the server and log the startup message -app.listen(port, () => { - logger.info('=============================== DockStat ===============================') - logger.info(`DockStatAPI is running on http://localhost:${port}/stats`); - logger.info(`Minimum timeout between stats queries is: ${queryInterval} milliseconds`); - logger.info(`The max size for Log files is: ${maxlogsize}MB`) - logger.info(`The amount of log files to keep is: ${LogAmount}`); - logger.info(`Secret Key: ${key}`) - logger.info(`Cup URL: ${cupUrl}`) - logger.info("Press Ctrl+C to stop the server."); - logger.info('========================================================================') -}); - -initializeHostQueues(); diff --git a/entrypoint.sh b/entrypoint.sh index df95b988..2008cdbd 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,7 +1,26 @@ -#!/usr/bin/env bash +#!/bin/bash -SECRET="${SECRET//\"}" +cat << EOF +Welcome to -export SECRET + ###### ###### #### ### ### #### ######### ###### ######### + ### ### ### ### ### ### ### ### ### ### ### ### + ### ### ### ### ### ###### #### ### ### ### ### + ### ### ### ### ### ### ### #### ### ############ ### + ### ### ### ### ### ### ### #### ### ### ### ### + ###### ###### #### ### ### #### ### ### ### ### (API) -exec npm run start \ No newline at end of file +Useful links: + +- Documentation: https://outline.itsnik.de/s/dockstat +- GitHub (Frontend): https://github.com/its4nik/dockstat +- GitHub (Backend): https://github.com/its4nik/dockstatapi +- API Documentation: http://localhost:7000/api-docs + +Summary: + +DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. + +EOF + +npm run start diff --git a/logger.js b/logger.js deleted file mode 100644 index ebaacc38..00000000 --- a/logger.js +++ /dev/null @@ -1,24 +0,0 @@ -const winston = require('winston'); -const yaml = require('yamljs'); -const config = yaml.load('./config/hosts.yaml'); - -const maxlogsize = config.log.logsize || 1; -const LogAmount = config.log.LogCount || 5; - -const logger = winston.createLogger({ - level: 'debug', - format: winston.format.combine( - winston.format.timestamp(), - winston.format.json() - ), - transports: [ - new winston.transports.Console(), - new winston.transports.File({ - filename: './logs/dockstat.log', - maxsize: 1024 * 1024 * maxlogsize, - maxFiles: LogAmount - }) - ] -}); - -module.exports = logger; \ No newline at end of file diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js new file mode 100644 index 00000000..8ee6a688 --- /dev/null +++ b/middleware/authMiddleware.js @@ -0,0 +1,50 @@ +const bcrypt = require("bcrypt"); +const fs = require("fs"); +const path = require("path"); +const logger = require("../utils/logger"); +const passwordFile = path.join(__dirname, "password.json"); +const passwordBool = path.join(__dirname, "usePassword.txt"); + +function authMiddleware(req, res, next) { + fs.readFile(passwordBool, "utf8", (err, data) => { + if (err) { + logger.error("Error reading the file:", err); + return; + } + + const isAuthEnabled = data.trim() === "true"; + + if (!isAuthEnabled) { + return next(); + } + + const providedPassword = req.headers["x-password"]; + if (!providedPassword) { + logger.error("Password required - Denied"); + return res.status(401).json({ message: "Password required" }); + } + + fs.readFile(passwordFile, "utf8", (err, data) => { + if (err) { + logger.error("Error reading password"); + return res.status(500).json({ message: "Error reading password" }); + } + + const storedData = JSON.parse(data); + bcrypt.compare(providedPassword, storedData.hash, (err, result) => { + if (err) { + logger.error("Error validating password - Denied access"); + return res.status(500).json({ message: "Error validating password" }); + } + if (!result) { + console.error("Invalid Password - Denied access"); + return res.status(401).json({ message: "Invalid password" }); + } + + next(); + }); + }); + }); +} + +module.exports = authMiddleware; diff --git a/middleware/password.json b/middleware/password.json new file mode 100644 index 00000000..37a7c4c4 --- /dev/null +++ b/middleware/password.json @@ -0,0 +1 @@ +{"hash":"$2b$10$qGcNmciEGhX.PiB.ofHib.Fob.nOjQNfguBoD4JDbbbTysrLrKGEi","salt":"$2b$10$qGcNmciEGhX.PiB.ofHib."} \ No newline at end of file diff --git a/middleware/usePassword.txt b/middleware/usePassword.txt new file mode 100644 index 00000000..02e4a84d --- /dev/null +++ b/middleware/usePassword.txt @@ -0,0 +1 @@ +false \ No newline at end of file diff --git a/misc/dependencyGraphs/mermaid-all.txt b/misc/dependencyGraphs/mermaid-all.txt new file mode 100644 index 00000000..87ba7da1 --- /dev/null +++ b/misc/dependencyGraphs/mermaid-all.txt @@ -0,0 +1,70 @@ +flowchart LR + +subgraph 0["config"] +1["db.js"] +2["swaggerConfig.js"] +9["dockerConfig.json"] +end +subgraph 3["controllers"] +4["containerController.js"] +7["fetchData.js"] +A["frontendConfiguration.js"] +B["scheduler.js"] +end +subgraph 5["utils"] +6["dockerClient.js"] +8["containerService.js"] +N["extractHostData.js"] +O["writeOfflineLog.js"] +U["rateLimiter.js"] +end +subgraph C["middleware"] +D["authMiddleware.js"] +end +subgraph E["routes"] +subgraph F["auth"] +G["routes.js"] +end +subgraph H["data"] +I["routes.js"] +end +subgraph J["frontendController"] +K["routes.js"] +end +subgraph L["getter"] +M["routes.js"] +end +subgraph P["setter"] +Q["routes.js"] +end +end +R["server.js"] +subgraph S["swagger"] +T["swaggerDocs.js"] +end +4-->6 +7-->1 +7-->8 +8-->9 +8-->6 +B-->1 +B-->7 +I-->1 +K-->A +M-->9 +M-->B +M-->8 +M-->6 +M-->N +M-->O +Q-->B +R-->B +R-->D +R-->G +R-->I +R-->K +R-->M +R-->Q +R-->T +R-->U +T-->2 diff --git a/misc/dependencyGraphs/mermaid-api.txt b/misc/dependencyGraphs/mermaid-api.txt new file mode 100644 index 00000000..c2dd6c86 --- /dev/null +++ b/misc/dependencyGraphs/mermaid-api.txt @@ -0,0 +1,33 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["getter"] +2["routes.js"] +end +end +subgraph 3["config"] +4["dockerConfig.json"] +7["db.js"] +end +subgraph 5["controllers"] +6["scheduler.js"] +8["fetchData.js"] +end +subgraph 9["utils"] +A["containerService.js"] +B["dockerClient.js"] +C["extractHostData.js"] +D["writeOfflineLog.js"] +end +2-->4 +2-->6 +2-->A +2-->B +2-->C +2-->D +6-->7 +6-->8 +8-->7 +8-->A +A-->4 +A-->B diff --git a/misc/dependencyGraphs/mermaid-auth.txt b/misc/dependencyGraphs/mermaid-auth.txt new file mode 100644 index 00000000..e7ab0669 --- /dev/null +++ b/misc/dependencyGraphs/mermaid-auth.txt @@ -0,0 +1,8 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["auth"] +2["routes.js"] +end +end + diff --git a/misc/dependencyGraphs/mermaid-conf.txt b/misc/dependencyGraphs/mermaid-conf.txt new file mode 100644 index 00000000..65e4b74a --- /dev/null +++ b/misc/dependencyGraphs/mermaid-conf.txt @@ -0,0 +1,26 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["setter"] +2["routes.js"] +end +end +subgraph 3["controllers"] +4["scheduler.js"] +7["fetchData.js"] +end +subgraph 5["config"] +6["db.js"] +A["dockerConfig.json"] +end +subgraph 8["utils"] +9["containerService.js"] +B["dockerClient.js"] +end +2-->4 +4-->6 +4-->7 +7-->6 +7-->9 +9-->A +9-->B diff --git a/misc/dependencyGraphs/mermaid-data.txt b/misc/dependencyGraphs/mermaid-data.txt new file mode 100644 index 00000000..e212edcb --- /dev/null +++ b/misc/dependencyGraphs/mermaid-data.txt @@ -0,0 +1,11 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["data"] +2["routes.js"] +end +end +subgraph 3["config"] +4["db.js"] +end +2-->4 diff --git a/misc/dependencyGraphs/mermaid-frontend.txt b/misc/dependencyGraphs/mermaid-frontend.txt new file mode 100644 index 00000000..35b4e61b --- /dev/null +++ b/misc/dependencyGraphs/mermaid-frontend.txt @@ -0,0 +1,11 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["frontendController"] +2["routes.js"] +end +end +subgraph 3["controllers"] +4["frontendConfiguration.js"] +end +2-->4 diff --git a/misc/entrypoint.sh b/misc/entrypoint.sh new file mode 100644 index 00000000..2008cdbd --- /dev/null +++ b/misc/entrypoint.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +cat << EOF +Welcome to + + ###### ###### #### ### ### #### ######### ###### ######### + ### ### ### ### ### ### ### ### ### ### ### ### + ### ### ### ### ### ###### #### ### ### ### ### + ### ### ### ### ### ### ### #### ### ############ ### + ### ### ### ### ### ### ### #### ### ### ### ### + ###### ###### #### ### ### #### ### ### ### ### (API) + +Useful links: + +- Documentation: https://outline.itsnik.de/s/dockstat +- GitHub (Frontend): https://github.com/its4nik/dockstat +- GitHub (Backend): https://github.com/its4nik/dockstatapi +- API Documentation: http://localhost:7000/api-docs + +Summary: + +DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. + +EOF + +npm run start diff --git a/modules/updateAvailable.js b/modules/updateAvailable.js deleted file mode 100644 index 1a25ce3e..00000000 --- a/modules/updateAvailable.js +++ /dev/null @@ -1,32 +0,0 @@ -const logger = require('../logger'); - -async function getData(target, url) { - - if (url === 'null') { - return false; - } - else { - try { - const response = await fetch(`${url}/json`, { - method: "GET" - }); - if (!response.ok) { - throw new Error(`Response status: ${response.status}`); - } - - const json = await response.json(); - - const images = json.images; - - for (const image in images) { - if (target === image) { - return images.hasOwnProperty(target); - } - } - } catch (error) { - logger.error(error.message); - } - } -} - -module.exports = getData; diff --git a/package-lock.json b/package-lock.json index 37c8cf27..68d93740 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,67 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "child_process": "^1.0.2", "cors": "^2.8.5", "dockerode": "^4.0.2", - "express": "^4.21.0", + "express": "^4.21.1", + "express-rate-limit": "^7.4.1", "node-fetch": "^3.3.2", - "winston": "^3.14.2", + "python-shell": "^5.0.0", + "sqlite3": "^5.1.7", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "winston": "^3.15.0", "yamljs": "^0.3.0" + }, + "devDependencies": { + "dependency-cruiser": "^16.5.0", + "nodemon": "^3.1.7" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" } }, "node_modules/@balena/dockerignore": { @@ -44,12 +98,161 @@ "kuler": "^2.0.0" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@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==", + "license": "MIT" + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -63,6 +266,178 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-jsx-walk": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz", + "integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn-loose": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.4.0.tgz", + "integrity": "sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", + "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -119,6 +494,20 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -128,6 +517,34 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bcrypt/node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -185,6 +602,19 @@ "concat-map": "0.0.1" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -226,6 +656,46 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -244,18 +714,99 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", "license": "ISC" }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -266,11 +817,24 @@ "color-string": "^1.6.0" } }, - "node_modules/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==", - "license": "MIT" + "node_modules/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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==", + "license": "MIT" }, "node_modules/color-string": { "version": "1.9.1", @@ -282,6 +846,15 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/color/node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -307,12 +880,27 @@ "text-hex": "1.0.x" } }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "license": "MIT" }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -334,9 +922,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -401,6 +989,30 @@ } } }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -417,6 +1029,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -425,6 +1043,71 @@ "node": ">= 0.8" } }, + "node_modules/dependency-cruiser": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.5.0.tgz", + "integrity": "sha512-6IELC3qRumlwhnbPLmcOK6WWdiGPFBw9a+D8DUsnTFpZ81tEtkAud4OPmU3OJFcuWS5VpgvKlctFkby5XDsGzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.13.0", + "acorn-jsx": "^5.3.2", + "acorn-jsx-walk": "^2.0.0", + "acorn-loose": "^8.4.0", + "acorn-walk": "^8.3.4", + "ajv": "^8.17.1", + "commander": "^12.1.0", + "enhanced-resolve": "^5.17.1", + "ignore": "^6.0.2", + "interpret": "^3.1.1", + "is-installed-globally": "^1.0.0", + "json5": "^2.2.3", + "memoize": "^10.0.0", + "picocolors": "^1.1.1", + "picomatch": "^4.0.2", + "prompts": "^2.4.2", + "rechoir": "^0.8.0", + "safe-regex": "^2.1.1", + "semver": "^7.6.3", + "teamcity-service-messages": "^0.1.14", + "tsconfig-paths-webpack-plugin": "^4.1.0", + "watskeburt": "^4.1.0" + }, + "bin": { + "depcruise": "bin/dependency-cruise.mjs", + "depcruise-baseline": "bin/depcruise-baseline.mjs", + "depcruise-fmt": "bin/depcruise-fmt.mjs", + "depcruise-wrap-stream-in-html": "bin/wrap-stream-in-html.mjs", + "dependency-cruise": "bin/dependency-cruise.mjs", + "dependency-cruiser": "bin/dependency-cruise.mjs" + }, + "engines": { + "node": "^18.17||>=20" + } + }, + "node_modules/dependency-cruiser/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/dependency-cruiser/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -434,6 +1117,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/docker-modem": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", @@ -463,11 +1155,29 @@ "node": ">= 8.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/enabled": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", @@ -482,6 +1192,29 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -491,6 +1224,37 @@ "once": "^1.4.0" } }, + "node_modules/enhanced-resolve": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, "node_modules/es-define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", @@ -515,6 +1279,15 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -523,17 +1296,27 @@ "node": ">= 0.6" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -564,6 +1347,21 @@ "node": ">= 0.10.0" } }, + "node_modules/express-rate-limit": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", + "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "4 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -579,6 +1377,20 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -608,6 +1420,25 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -679,12 +1510,39 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -693,6 +1551,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -711,6 +1590,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -732,6 +1617,45 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-directory/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -743,6 +1667,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -776,6 +1717,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC" + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -787,6 +1734,13 @@ "node": ">= 0.4" } }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "license": "BSD-2-Clause", + "optional": true + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -802,6 +1756,44 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -833,6 +1825,50 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -850,21 +1886,166 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/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==", + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">=10.13.0" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", + "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-directory": "^4.0.1", + "is-path-inside": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", + "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -877,12 +2058,92 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "optional": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT", + "optional": true + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/logform": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", @@ -900,6 +2161,71 @@ "node": ">= 12.0.0" } }, + "node_modules/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==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -908,6 +2234,22 @@ "node": ">= 0.6" } }, + "node_modules/memoize": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", + "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -957,6 +2299,31 @@ "node": ">= 0.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -969,6 +2336,122 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mkdirp-classic": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", @@ -988,6 +2471,12 @@ "license": "MIT", "optional": true }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -997,6 +2486,24 @@ "node": ">= 0.6" } }, + "node_modules/node-abi": { + "version": "3.71.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", + "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -1034,6 +2541,102 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1083,6 +2686,29 @@ "fn.name": "1.x.x" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, + "node_modules/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1100,11 +2726,99 @@ "node": ">=0.10.0" } }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, "node_modules/path-to-regexp": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1118,6 +2832,13 @@ "node": ">= 0.10" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -1128,6 +2849,15 @@ "once": "^1.3.1" } }, + "node_modules/python-shell": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-5.0.0.tgz", + "integrity": "sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -1164,6 +2894,21 @@ "node": ">= 0.8" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -1178,6 +2923,96 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/regexp-tree": { + "version": "0.1.27", + "resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz", + "integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==", + "dev": true, + "license": "MIT", + "bin": { + "regexp-tree": "bin/regexp-tree" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -1198,6 +3033,16 @@ ], "license": "MIT" }, + "node_modules/safe-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz", + "integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "regexp-tree": "~0.1.1" + } + }, "node_modules/safe-stable-stringify": { "version": "2.4.3", "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", @@ -1213,6 +3058,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1276,6 +3133,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -1297,30 +3160,142 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "license": "MIT", + "optional": true, "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", "license": "MIT", + "optional": true, "dependencies": { - "is-arrayish": "^0.3.1" + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" } }, "node_modules/split-ca": { @@ -1335,6 +3310,30 @@ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "license": "BSD-3-Clause" }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, "node_modules/ssh2": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", @@ -1352,6 +3351,19 @@ "nan": "^2.18.0" } }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -1378,6 +3390,178 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.17.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", + "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==", + "license": "Apache-2.0" + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", @@ -1406,12 +3590,50 @@ "node": ">=6" } }, + "node_modules/tar/node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/teamcity-service-messages": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz", + "integrity": "sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w==", + "dev": true, + "license": "MIT" + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1420,6 +3642,22 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -1429,6 +3667,48 @@ "node": ">= 14.0.0" } }, + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tsconfig-paths-webpack-plugin": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", + "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.7.0", + "tsconfig-paths": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", @@ -1447,6 +3727,33 @@ "node": ">= 0.6" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1470,6 +3777,15 @@ "node": ">= 0.4.0" } }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -1479,6 +3795,19 @@ "node": ">= 0.8" } }, + "node_modules/watskeburt": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-4.1.0.tgz", + "integrity": "sha512-KkY5H51ajqy9HYYI+u9SIURcWnqeVVhdH0I+ab6aXPGHfZYxgRCwnR6Lm3+TYB6jJVt5jFqw4GAKmwf1zHmGQw==", + "dev": true, + "license": "MIT", + "bin": { + "watskeburt": "dist/run-cli.js" + }, + "engines": { + "node": "^18||>=20" + } + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -1488,10 +3817,51 @@ "node": ">= 8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/winston": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", - "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", + "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", @@ -1530,6 +3900,21 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yamljs": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", @@ -1543,6 +3928,36 @@ "json2yaml": "bin/json2yaml", "yaml2json": "bin/yaml2json" } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 372caad0..bd38ba88 100644 --- a/package.json +++ b/package.json @@ -2,21 +2,33 @@ "name": "dockstatapi", "version": "1.0.0", "description": "API for docker hosts using dockerode", - "main": "dockerstatsapi.js", + "main": "server.js", "scripts": { - "start": "node dockstatapi.js", - "test": "echo \"Error: no test specified\" && exit 1" + "start": "node server.js", + "dev": "nodemon server.js", + "offline": "OFFLINE=true nodemon server.js", + "dep": "bash ./utils/createDependencyGraph.sh" }, "keywords": [], "author": "Its4Nik", "license": "ISC", "dependencies": { + "bcrypt": "^5.1.1", "child_process": "^1.0.2", "cors": "^2.8.5", "dockerode": "^4.0.2", - "express": "^4.21.0", + "express": "^4.21.1", + "express-rate-limit": "^7.4.1", "node-fetch": "^3.3.2", - "winston": "^3.14.2", + "python-shell": "^5.0.0", + "sqlite3": "^5.1.7", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", + "winston": "^3.15.0", "yamljs": "^0.3.0" + }, + "devDependencies": { + "dependency-cruiser": "^16.5.0", + "nodemon": "^3.1.7" } -} \ No newline at end of file +} diff --git a/routes/auth/routes.js b/routes/auth/routes.js new file mode 100644 index 00000000..a0b217e4 --- /dev/null +++ b/routes/auth/routes.js @@ -0,0 +1,145 @@ +const express = require("express"); +const bcrypt = require("bcrypt"); +const fs = require("fs"); +const path = require("path"); +const logger = require("../../utils/logger"); +const router = express.Router(); +const passwordFile = path.join(__dirname, "../../middleware/password.json"); +const passwordBool = path.join(__dirname, "../../middleware/usePassword.txt"); +const saltRounds = 10; + +function setTrue() { + fs.writeFile(passwordBool, "true", "utf8", (err) => { + if (err) { + logger.error("Error writing to the file:", err); + return; + } + logger.info(`Status "true" has been written to the file.`); + }); +} + +function setFalse() { + fs.writeFile(passwordBool, "false", "utf8", (err) => { + if (err) { + logger.error("Error writing to the file:", err); + return; + } + logger.info(`Status "false" has been written to the file.`); + }); +} + +/** + * @swagger + * /auth/enable: + * post: + * summary: Enable authentication by setting a password + * tags: [Authentication] + * parameters: + * - name: password + * in: query + * required: true + * responses: + * 200: + * description: Authentication enabled. + * 400: + * description: Password is required. + * 500: + * description: Error saving password. + */ +router.post("/enable", (req, res) => { + fs.readFile(passwordBool, "utf8", (err, data) => { + const password = req.query.password; + if (err) { + logger.error("Error reading the file:", err); + return; + } + + const isAuthEnabled = data.trim() === "true"; + if (isAuthEnabled) { + logger.error( + "Passowrd Authentication is already enabled, please dactivate it first", + ); + return res.status(401).json({ + message: + "Passowrd Authentication is already enabled, please dactivate it first", + }); + } + + if (!password) { + return res.status(400).json({ message: "Password is required" }); + } + + bcrypt.genSalt(saltRounds, (err, salt) => { + if (err) { + logger.error("Error generating salt"); + return res.status(500).json({ message: "Error generating salt" }); + } + + bcrypt.hash(password, salt, (err, hash) => { + if (err) { + logger.error("Error hashing password"); + return res.status(500).json({ message: "Error hashing password" }); + } + + const passwordData = { hash, salt }; + fs.writeFile(passwordFile, JSON.stringify(passwordData), (err) => { + if (err) + return res.status(500).json({ message: "Error saving password" }); + setTrue(); + res.json({ message: "Authentication enabled" }); + }); + }); + }); + }); +}); + +/** + * @swagger + * /auth/disable: + * post: + * summary: Disable authentication by providing the existing password + * tags: [Authentication] + * parameters: + * - name: password + * in: query + * required: true + * responses: + * 200: + * description: Authentication disabled. + * 400: + * description: Password is required. + * 401: + * description: Invalid password. + * 500: + * description: Error disabling authentication. + */ +router.post("/disable", (req, res) => { + const password = req.query.password; + if (!password) { + logger.error("Password is required!"); + return res.status(400).json({ message: "Password is required" }); + } + + fs.readFile(passwordFile, "utf8", (err, data) => { + if (err) { + logger.error("Error reading password"); + return res.status(500).json({ message: "Error reading password" }); + } + + const storedData = JSON.parse(data); + bcrypt.compare(password, storedData.hash, (err, result) => { + if (err) { + logger.error("Error validating password"); + return res.status(500).json({ message: "Error validating password" }); + } + if (!result) { + logger.error("Invalid password"); + return res.status(401).json({ message: "Invalid password" }); + } + setFalse(); + res.json({ message: "Authentication disabled" }); + }); + }); +}); + +module.exports = router; diff --git a/routes/data/routes.js b/routes/data/routes.js new file mode 100644 index 00000000..adce8d79 --- /dev/null +++ b/routes/data/routes.js @@ -0,0 +1,111 @@ +const express = require("express"); +const router = express.Router(); +const db = require("../../config/db"); +const logger = require("../../utils/logger"); + +function formatRows(rows) { + return rows.reduce((acc, row, index) => { + acc[index] = JSON.parse(row.info); + return acc; + }, {}); +} + +/** + * @swagger + * /data/latest: + * get: + * summary: Retrieve the latest entry from the database + * tags: [Database queries] + * responses: + * 200: + * description: A JSON object containing the latest entry's 'info' data. + * content: + * application/json: + * schema: + * type: object + * example: + * name: "Container A" + * id: "abcd1234" + * cpu_usage: 30 + * mem_usage: 2048 + */ +router.get("/latest", (req, res) => { + db.get( + "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", + (err, row) => { + if (err) { + logger.error("Error fetching latest data:", err.message); + return res.status(500).json({ error: "Internal server error" }); + } + res.json(JSON.parse(row.info)); + }, + ); +}); + +/** + * @swagger + * /data/time/24h: + * get: + * summary: Retrieve entries from the last 24 hours from the database + * tags: [Database queries] + * responses: + * 200: + * description: A numbered array of 'info' JSON objects from the last 24 hours. + * content: + * application/json: + * schema: + * type: object + * example: + * 0: + * name: "Container A" + * id: "abcd1234" + * cpu_usage: 30 + * mem_usage: 2048 + * 1: + * name: "Container B" + * id: "efgh5678" + * cpu_usage: 45 + * mem_usage: 3072 + */ +router.get("/time/24h", (req, res) => { + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + db.all( + "SELECT info FROM data WHERE timestamp >= ?", + [oneDayAgo], + (err, rows) => { + if (err) { + logger.error("Error fetching data from last 24 hours:", err.message); + return res.status(500).json({ error: "Internal server error" }); + } + res.json(formatRows(rows)); + }, + ); +}); + +/** + * @swagger + * /data/clear: + * delete: + * summary: Clear all entries from the database + * tags: [Database queries] + * responses: + * 200: + * description: A message indicating whether the database was cleared successfully. + * content: + * application/json: + * schema: + * type: object + * example: + * message: "Database cleared successfully." + */ +router.delete("/clear", (req, res) => { + db.run("DELETE FROM data", (err) => { + if (err) { + logger.error("Error clearing the database:", err.message); + return res.status(500).json({ error: "Internal server error" }); + } + res.json({ message: "Database cleared successfully" }); + }); +}); + +module.exports = router; diff --git a/routes/frontendController/routes.js b/routes/frontendController/routes.js new file mode 100644 index 00000000..986276fa --- /dev/null +++ b/routes/frontendController/routes.js @@ -0,0 +1,340 @@ +const express = require("express"); +const router = express.Router(); +const logger = require("../../utils/logger"); +const { + hideContainer, + unhideContainer, + addTagToContainer, + removeTagFromContainer, + pinContainer, + unpinContainer, +} = require("../../controllers/frontendConfiguration"); + +/** + * @swagger + * /frontend/hide/{containerName}: + * post: + * summary: Hide a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to hide + * responses: + * 200: + * description: Container hidden successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Hide a container +router.post("/hide/:containerName", async (req, res) => { + const { containerName } = req.params; + const target = containerName; + //console.log(target); + + try { + await hideContainer(target); + res.json({ success: true, message: `Container, ${target}, hidden.` }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/unhide/{containerName}: + * post: + * summary: Unhide a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to unhide + * responses: + * 200: + * description: Container unhidden successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Unhide a container +router.post("/unhide/:containerName", async (req, res) => { + const { containerName } = req.params; + try { + await unhideContainer(containerName); + res.json({ success: true, message: "Container unhidden successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/tag/{containerName}/{tag}: + * post: + * summary: Add a tag to a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to add tag to + * - in: path + * name: tag + * schema: + * type: string + * required: true + * description: The tag to add + * responses: + * 200: + * description: Tag added successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Add a tag to a container +router.post("/tag/:containerName/:tag", async (req, res) => { + const { containerName, tag } = req.params; + try { + await addTagToContainer(containerName, tag); + res.json({ success: true, message: "Tag added successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/remove-tag/{containerName}/{tag}: + * post: + * summary: Remove a tag from a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to remove tag from + * - in: path + * name: tag + * schema: + * type: string + * required: true + * description: The tag to remove + * responses: + * 200: + * description: Tag removed successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Remove a tag from a container +router.post("/remove-tag/:containerName/:tag", async (req, res) => { + const { containerName, tag } = req.params; + try { + await removeTagFromContainer(containerName, tag); + res.json({ success: true, message: "Tag removed successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/pin/{containerName}: + * post: + * summary: Pin a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to pin + * responses: + * 200: + * description: Container pinned successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Pin a container +router.post("/pin/:containerName", async (req, res) => { + const { containerName } = req.params; + try { + await pinContainer(containerName); + res.json({ success: true, message: "Container pinned successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/unpin/{containerName}: + * post: + * summary: Unpin a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to unpin + * responses: + * 200: + * description: Container unpinned successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Unpin a container +router.post("/unpin/:containerName", async (req, res) => { + const { containerName } = req.params; + try { + await unpinContainer(containerName); + res.json({ success: true, message: "Container unpinned successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +module.exports = router; diff --git a/routes/getter/routes.js b/routes/getter/routes.js new file mode 100644 index 00000000..6c5fbc0d --- /dev/null +++ b/routes/getter/routes.js @@ -0,0 +1,334 @@ +const extractRelevantData = require("../../utils/extractHostData"); +const express = require("express"); +const router = express.Router(); +const { + writeOfflineLog, + readOfflineLog, +} = require("../../utils/writeOfflineLog"); +const { getDockerClient } = require("../../utils/dockerClient"); +const { fetchAllContainers } = require("../../utils/containerService"); +const { getCurrentSchedule } = require("../../controllers/scheduler"); +const logger = require("../../utils/logger"); +const path = require("path"); +const fs = require("fs"); + +/** + * @swagger + * /api/hosts: + * get: + * summary: Retrieve a list of all available Docker hosts + * tags: [Hosts] + * responses: + * 200: + * description: A JSON object containing an array of host names. + * content: + * application/json: + * schema: + * type: object + * properties: + * hosts: + * type: array + * items: + * type: string + * example: ["local", "remote1"] + */ + +router.get("/hosts", (req, res) => { + const config = require("../../config/dockerConfig.json"); + const hosts = config.hosts.map((host) => host.name); + logger.info("Fetching all available Docker hosts"); + res.status(200).json({ hosts }); +}); + +/** + * @swagger + * /api/host/{hostName}/stats: + * get: + * summary: Retrieve statistics for a specified Docker host + * tags: [Hosts] + * parameters: + * - name: hostName + * in: path + * required: true + * description: The name of the host for which to fetch statistics. + * schema: + * type: string + * responses: + * 200: + * description: A JSON object containing relevant statistics for the specified host. + * content: + * application/json: + * schema: + * type: object + * properties: + * hostName: + * type: string + * description: The name of the Docker host. + * info: + * type: object + * description: Information about the Docker host (e.g., storage, running containers). + * version: + * type: object + * description: Version details of the Docker installation on the host. + * 500: + * description: An error occurred while fetching host statistics. + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * description: Error message detailing the issue encountered. + */ +router.get("/host/:hostName/stats", async (req, res) => { + const hostName = req.params.hostName; + logger.info(`Fetching stats for host: ${hostName}`); + if (process.env.OFFLINE === "true") { + logger.info("Fetching offline Host Stats"); + res.status(200).json(readOfflineLog); + } else { + try { + const docker = getDockerClient(hostName); + const info = await docker.info(); + const version = await docker.version(); + const relevantData = extractRelevantData({ hostName, info, version }); + + writeOfflineLog(JSON.stringify(relevantData)); + res.status(200).json(relevantData); + } catch (error) { + logger.error( + `Error fetching stats for host: ${hostName} - ${error.message || "Unknown error"}`, + ); + res.status(500).json({ + error: `Error fetching host stats: ${error.message || "Unknown error"}`, + }); + } + } +}); + +/** + * @swagger + * /api/containers: + * get: + * summary: Retrieve all Docker containers across all configured hosts + * tags: [Containers] + * responses: + * 200: + * description: A JSON object containing container data for all hosts. + * content: + * application/json: + * schema: + * type: object + * additionalProperties: + * type: object + * properties: + * name: + * type: string + * description: Name of the container. + * id: + * type: string + * description: Unique identifier for the container. + * hostName: + * type: string + * description: The host on which the container is running. + * state: + * type: string + * description: Current state of the container (e.g., running, exited). + * cpu_usage: + * type: number + * format: double + * description: CPU usage in nanoseconds. + * mem_usage: + * type: number + * description: Memory usage in bytes. + * mem_limit: + * type: number + * description: Memory limit in bytes. + * net_rx: + * type: number + * description: Total received bytes over the network. + * net_tx: + * type: number + * description: Total transmitted bytes over the network. + * current_net_rx: + * type: number + * description: Current received bytes over the network. + * current_net_tx: + * type: number + * description: Current transmitted bytes over the network. + * networkMode: + * type: string + * description: Network mode configured for the container. + * link: + * type: string + * description: Optional link to additional information. + * icon: + * type: string + * description: Optional icon representing the container. + * tags: + * type: string + * description: Optional tags associated with the container. + * 500: + * description: An error occurred while fetching container data. + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * description: Error message detailing the issue encountered. + */ +router.get("/containers", async (req, res) => { + logger.info("Fetching all containers across all hosts"); + try { + const allContainerData = await fetchAllContainers(); + res.status(200).json(allContainerData); + } catch (error) { + logger.error(`Error fetching containers: ${error.message}`); + res.status(500).json({ error: "Failed to fetch containers" }); + } +}); + +/** + * @swagger + * /api/config: + * get: + * summary: Retrieve Docker configuration + * tags: [Configuration] + * responses: + * 200: + * description: A JSON object containing the Docker configuration. + * content: + * application/json: + * schema: + * type: object + * additionalProperties: true + * 500: + * description: An error occurred while loading the Docker configuration. + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * description: Error message detailing the issue encountered. + */ +router.get("/config", async (req, res) => { + const configPath = path.join(__dirname, "../../config/dockerConfig.json"); + try { + const rawData = fs.readFileSync(configPath); + const jsonData = JSON.parse(rawData.toString()); + res.status(200).json(jsonData); + } catch (error) { + logger.error("Error loading dockerConfig.json: " + error.message); + res.status(500).json({ error: "Failed to load Docker configuration" }); + } +}); + +/** + * @swagger + * /api/current-schedule: + * get: + * summary: Get the current fetch schedule in seconds + * tags: [Configuration] + * responses: + * 200: + * description: Current fetch schedule retrieved successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * interval: + * type: integer + * description: Current fetch interval in seconds. + */ +router.get("/current-schedule", (req, res) => { + const currentSchedule = getCurrentSchedule(); + res.json(currentSchedule); +}); + +/** + * @swagger + * /api/status: + * get: + * summary: Check server status + * tags: [Misc] + * description: Returns a 200 status with an "up" message to indicate the server is up and running. Used for Healthchecks + * responses: + * 200: + * description: Server is running + * content: + * application/json: + * schema: + * type: object + * properties: + * status: + * type: string + * example: "up" + */ +router.get("/status", (req, res) => { + res.status(200).json({ status: "up" }); +}); + +/** + * @swagger + * /api/frontend-config: + * get: + * summary: Get Frontend Configuration + * tags: [Configuration] + * description: Retrieves the frontend configuration data. + * responses: + * 200: + * description: Success + * content: + * application/json: + * schema: + * type: array + * items: + * type: object + * properties: + * name: + * type: string + * description: Container Name + * hidden: + * type: boolean + * description: Whether the container is hidden + * tags: + * type: array + * items: + * type: string + * description: Tags associated with the container + * pinned: + * type: boolean + * description: Whether the container is pinned + * 500: + * description: Internal Server Error + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * description: Error message + */ +router.get("/frontend-config", (req, res) => { + const configPath = path.join( + __dirname, + "../../data/frontendConfiguration.json", + ); + try { + const rawData = fs.readFileSync(configPath); + const jsonData = JSON.parse(rawData.toString()); + res.status(200).json(jsonData); + } catch (error) { + logger.error("Error loading frontendConfiguration.json: " + error.message); + res.status(500).json({ error: "Failed to load Frontend configuration" }); + } +}); + +module.exports = router; diff --git a/routes/setter/routes.js b/routes/setter/routes.js new file mode 100644 index 00000000..24ae2ad9 --- /dev/null +++ b/routes/setter/routes.js @@ -0,0 +1,145 @@ +const { + setFetchInterval, + parseInterval, +} = require("../../controllers/scheduler"); +const express = require("express"); +const router = express.Router(); +const path = require("path"); +const fs = require("fs"); +const logger = require("../../utils/logger"); + +/** + * @swagger + * /conf/addHost: + * put: + * summary: Add a new host to the Docker configuration + * tags: [Configuration] + * parameters: + * - name: name + * in: query + * required: true + * description: The name of the new host. + * - name: url + * in: query + * required: true + * description: The URL of the new host. + * - name: port + * in: query + * required: true + * description: The port of the new host. + * responses: + * 200: + * description: Host added successfully. + * 400: + * description: Bad request, invalid input. + * 500: + * description: An error occurred while adding the host. + */ +router.put("/addHost", async (req, res) => { + const name = req.query.name; + const url = req.query.url; + const port = req.query.port; + const configPath = path.join(__dirname, "../../config/dockerConfig.json"); + + if (!name || !url || !port) { + return res.status(400).json({ error: "Name, Port and URL are required." }); + } + + try { + const rawData = fs.readFileSync(configPath); + const config = JSON.parse(rawData); + + // Check for existing host + if (config.hosts.some((host) => host.name === name)) { + return res.status(400).json({ error: "Host already exists." }); + } + + config.hosts.push({ name, url, port }); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + logger.info(`Added new host: ${name}`); + res.status(200).json({ message: "Host added successfully." }); + } catch (error) { + logger.error("Error adding host: " + error.message); + res.status(500).json({ error: "Failed to add host." }); + } +}); + +/** + * @swagger + * /conf/scheduler: + * put: + * summary: Set fetch interval for data fetching + * tags: [Configuration] + * parameters: + * - name: interval + * in: query + * required: true + * description: The new interval for fetching data, e.g., "6h 20m", "300s". + * responses: + * 200: + * description: Fetch interval set successfully. + * 400: + * description: Invalid interval format or out of range. + */ +router.put("/scheduler", (req, res) => { + const interval = req.query.interval; + const newInterval = parseInterval(interval); + + if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { + return res + .status(400) + .json({ error: "Interval must be between 5 minutes and 6 hours." }); + } + + setFetchInterval(newInterval); + res.json({ message: `Fetch interval set to ${interval}.` }); +}); + +/** + * @swagger + * /conf/removeHost: + * delete: + * summary: Remove a host from the Docker configuration + * tags: [Configuration] + * parameters: + * - name: hostName + * in: query + * required: true + * description: The name of the host to remove. + * responses: + * 200: + * description: Host removed successfully. + * 404: + * description: Host not found. + * 500: + * description: An error occurred while removing the host. + */ +router.delete("/removeHost", async (req, res) => { + const hostName = req.query.hostName; + const configPath = path.join(__dirname, "../../config/dockerConfig.json"); + + if (!hostName) { + return res.status(400).json({ error: "Host name is required." }); + } + + try { + const rawData = fs.readFileSync(configPath); + const config = JSON.parse(rawData); + + // Check for existing host + const hostIndex = config.hosts.findIndex((host) => host.name === hostName); + if (hostIndex === -1) { + return res.status(404).json({ error: "Host not found." }); + } + + config.hosts.splice(hostIndex, 1); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + logger.info(`Removed host: ${hostName}`); + res.status(200).json({ message: "Host removed successfully." }); + } catch (error) { + logger.error("Error removing host: " + error.message); + res.status(500).json({ error: "Failed to remove host." }); + } +}); + +module.exports = router; diff --git a/scripts/install_apprise.sh b/scripts/install_apprise.sh deleted file mode 100644 index 7506d0e8..00000000 --- a/scripts/install_apprise.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -VENV_DIR="/api" - -apk update -apk add python3 py3-pip py3-virtualenv - -python3 -m venv "$VENV_DIR" - -. "$VENV_DIR/bin/activate" - -pip install apprise - -deactivate - -echo "Apprise has been successfully installed in the virtual environment." diff --git a/scripts/notify.sh b/scripts/notify.sh deleted file mode 100755 index 54dc2262..00000000 --- a/scripts/notify.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -NOTIFY_TYPE=$1 # ADD, REMOVE, EXIT, ANY -CONTAINER_ID=$2 # Container ID -CONTAINER_NAME=$3 # Container Name -HOST=$4 # Host Name -STATE=$5 # Current State - -ADD_MESSAGE="${ADD_MESSAGE:-🆕 Container Added: $CONTAINER_NAME ($CONTAINER_ID) on $HOST}" -REMOVE_MESSAGE="${REMOVE_MESSAGE:-🚫 Container Removed: $CONTAINER_NAME ($CONTAINER_ID) on $HOST}" -EXIT_MESSAGE="${EXIT_MESSAGE:-❌ Container Exited: $CONTAINER_NAME ($CONTAINER_ID) on $HOST}" -ANY_MESSAGE="${ANY_MESSAGE:-⚠️ Container State Changed: $CONTAINER_NAME ($CONTAINER_ID) on $HOST - New State: $STATE}" - -case "$NOTIFY_TYPE" in - ADD) - MESSAGE="$ADD_MESSAGE" - ;; - REMOVE) - MESSAGE="$REMOVE_MESSAGE" - ;; - EXIT) - MESSAGE="$EXIT_MESSAGE" - ;; - ANY) - MESSAGE="$ANY_MESSAGE" - ;; - *) - MESSAGE="Unknown action for $CONTAINER_NAME ($CONTAINER_ID) on $HOST" - ;; -esac - -if [[ ! -f ./config/apprise_config.yml ]]; then - echo -n "No Apprise configuration found, aborting." - exit 1 -fi - -# Send notification via Apprise - -### PYTHON ENVIRONMENT: ### -. /api/bin/activate - -apprise -b "$MESSAGE" --config ./config/apprise_config.yml - -deactivate -########################### - -exit 0 diff --git a/server.js b/server.js new file mode 100644 index 00000000..b7a2731c --- /dev/null +++ b/server.js @@ -0,0 +1,33 @@ +const express = require("express"); +const swaggerDocs = require("./swagger/swaggerDocs"); +const api = require("./routes/getter/routes"); +const conf = require("./routes/setter/routes"); +const auth = require("./routes/auth/routes"); +const data = require("./routes/data/routes"); +const frontend = require("./routes/frontendController/routes"); +const authMiddleware = require("./middleware/authMiddleware"); +const app = express(); +const logger = require("./utils/logger"); +const { scheduleFetch } = require("./controllers/scheduler"); +const { limiter } = require("./utils/rateLimiter"); + +const PORT = "7070"; + +app.use(express.json()); + +app.use("/api-docs", (req, res, next) => next()); + +swaggerDocs(app); +scheduleFetch(); + +// Routes +app.use("/api", authMiddleware, api); +app.use("/conf", authMiddleware, conf); +app.use("/auth", authMiddleware, auth); +app.use("/data", authMiddleware, data); +app.use("/frontend", authMiddleware, frontend); + +app.listen(PORT, () => { + logger.info(`Server is running on http://localhost:${PORT}`); + logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs`); +}); diff --git a/swagger/swaggerDocs.js b/swagger/swaggerDocs.js new file mode 100644 index 00000000..57193722 --- /dev/null +++ b/swagger/swaggerDocs.js @@ -0,0 +1,10 @@ +const swaggerUi = require("swagger-ui-express"); +const swaggerJsdoc = require("swagger-jsdoc"); +const swaggerConfig = require("../config/swaggerConfig"); + +const swaggerDocs = (app) => { + const specs = swaggerJsdoc(swaggerConfig); + app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs)); +}; + +module.exports = swaggerDocs; diff --git a/utils/containerService.js b/utils/containerService.js new file mode 100644 index 00000000..eb078a55 --- /dev/null +++ b/utils/containerService.js @@ -0,0 +1,63 @@ +const config = require("../config/dockerConfig.json"); +const logger = require("./logger"); +const { getDockerClient } = require("./dockerClient"); + +async function fetchAllContainers() { + const allContainerData = {}; + + for (const hostConfig of config.hosts) { + const hostName = hostConfig.name; + try { + const docker = getDockerClient(hostName); + const containers = await docker.listContainers({ all: true }); + + allContainerData[hostName] = await Promise.all( + containers.map(async (container) => { + const containerInfo = await docker + .getContainer(container.Id) + .inspect(); + const containerStats = await docker + .getContainer(container.Id) + .stats({ stream: false }); + const cpuDelta = + containerStats.cpu_stats.cpu_usage.total_usage - + containerStats.precpu_stats.cpu_usage.total_usage; + const systemCpuDelta = + containerStats.cpu_stats.system_cpu_usage - + containerStats.precpu_stats.system_cpu_usage; + const cpuUsage = + systemCpuDelta > 0 + ? (cpuDelta / systemCpuDelta) * + containerStats.cpu_stats.online_cpus + : 0; + + return { + name: container.Names[0].replace("/", ""), + id: container.Id, + hostName: hostName, + state: container.State, + cpu_usage: cpuUsage * 1000000000, + mem_usage: containerStats.memory_stats.usage, + mem_limit: containerStats.memory_stats.limit, + net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + current_net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + networkMode: containerInfo.HostConfig.NetworkMode, + }; + }), + ); + } catch (error) { + logger.error( + `Error fetching containers for host: ${hostName} - ${error.message}`, + ); + allContainerData[hostName] = { + error: `Error fetching containers: ${error.message}`, + }; + } + } + + return allContainerData; +} + +module.exports = { fetchAllContainers }; diff --git a/utils/createDependencyGraph.sh b/utils/createDependencyGraph.sh new file mode 100755 index 00000000..3e75de0a --- /dev/null +++ b/utils/createDependencyGraph.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +TMP=$(mktemp) + +cat ./server.js | grep "./routes" | awk '{print $2,$4}' > $TMP + +while read line; do + target_route=$(echo "$line" | cut -d '"' -f2) + route=$(echo "$line" | awk '{print $1}') + + echo + echo "Route: $route" + echo ${target_route}.js + + + npx depcruise \ + -p cli-feedback \ + -T mermaid \ + -x "^node_modules|logger|.dependency-cruiser|path|fs" \ + -f ./misc/dependencyGraphs/mermaid-${route}.txt \ + ${target_route}.js + +done < <(cat $TMP) + +npx depcruise \ + -p cli-feedback \ + -T mermaid \ + -x "^node_modules|logger|.dependency-cruiser|path|fs" \ + -f ./misc/dependencyGraphs/mermaid-all.txt \ + ./ + +sleep 0.5 + +echo -e "\n========\n\n DONE\n\n========" diff --git a/utils/dockerClient.js b/utils/dockerClient.js new file mode 100644 index 00000000..3d691e50 --- /dev/null +++ b/utils/dockerClient.js @@ -0,0 +1,45 @@ +const Docker = require("dockerode"); +const fs = require("fs"); +const path = require("path"); +const logger = require("./logger"); + +// Function to dynamically load config on each request +function loadDockerConfig() { + const configPath = path.join(__dirname, "../config/dockerConfig.json"); + try { + const rawData = fs.readFileSync(configPath); + logger.debug("Refreshed DockerConfig.json"); + return JSON.parse(rawData); + } catch (error) { + logger.error("Error loading dockerConfig.json: " + error.message); + throw new Error("Failed to load Docker configuration"); + } +} + +// Function to create the Docker client using separate url and port +function createDockerClient(hostConfig) { + logger.info( + `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port}`, + ); + return new Docker({ + host: hostConfig.url, + port: hostConfig.port || 2375, // Use 2375 as default port for non-TLS + protocol: "http", // Ensure the use of http for non-TLS + }); +} + +// This function will get the Docker client based on the host configuration +const getDockerClient = (hostName) => { + logger.debug(`Getting Docker Client for ${hostName}`); + const config = loadDockerConfig(); // Dynamically load config + const hostConfig = config.hosts.find((host) => host.name === hostName); + + if (!hostConfig) { + const errorMsg = `Docker host ${hostName} not found in configuration`; + logger.error(errorMsg); + throw new Error(errorMsg); + } + return createDockerClient(hostConfig); +}; + +module.exports = { getDockerClient }; diff --git a/utils/extractHostData.js b/utils/extractHostData.js new file mode 100644 index 00000000..87db239f --- /dev/null +++ b/utils/extractHostData.js @@ -0,0 +1,26 @@ +function extractRelevantData(jsonData) { + return { + hostName: jsonData.hostName, + info: { + ID: jsonData.info.ID, + Containers: jsonData.info.Containers, + ContainersRunning: jsonData.info.ContainersRunning, + ContainersPaused: jsonData.info.ContainersPaused, + ContainersStopped: jsonData.info.ContainersStopped, + Images: jsonData.info.Images, + OperatingSystem: jsonData.info.OperatingSystem, + KernelVersion: jsonData.info.KernelVersion, + Architecture: jsonData.info.Architecture, + MemTotal: jsonData.info.MemTotal, + NCPU: jsonData.info.NCPU, + }, + version: { + Components: jsonData.version.Components.reduce((acc, component) => { + acc[component.Name] = component.Version; + return acc; + }, {}), + }, + }; +} + +module.exports = extractRelevantData; diff --git a/utils/logger.js b/utils/logger.js new file mode 100644 index 00000000..853ca6fc --- /dev/null +++ b/utils/logger.js @@ -0,0 +1,20 @@ +const winston = require("winston"); +const loggerConfig = require("../config/loggerConfig"); + +const transports = [new winston.transports.Console()]; + +if (loggerConfig.transports.file.enabled) { + transports.push( + new winston.transports.File({ + filename: loggerConfig.transports.file.filename, + }), + ); +} + +const logger = winston.createLogger({ + level: loggerConfig.level, + format: loggerConfig.format, + transports, +}); + +module.exports = logger; diff --git a/utils/rateLimiter.js b/utils/rateLimiter.js new file mode 100644 index 00000000..c323c581 --- /dev/null +++ b/utils/rateLimiter.js @@ -0,0 +1,8 @@ +import { rateLimit } from "express-rate-limit"; + +export const limiter = rateLimit({ + windowMs: 5 * 60 * 1000, // 5 minutes + limit: 300, // Limit each IP to 300 requests per `window` (here, per 5 minutes) + standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers + legacyHeaders: false, // Disable the `X-RateLimit-*` headers +}); diff --git a/utils/writeOfflineLog.js b/utils/writeOfflineLog.js new file mode 100644 index 00000000..4d26b1d5 --- /dev/null +++ b/utils/writeOfflineLog.js @@ -0,0 +1,31 @@ +const fs = require("fs"); +const path = require("path"); +const logger = require("../utils/logger"); + +const LOG_FILE_PATH = path.join(__dirname, "../logs/hostStats.json"); + +function writeOfflineLog(message) { + try { + if (!fs.existsSync(LOG_FILE_PATH)) { + fs.writeFileSync(LOG_FILE_PATH, message); + } + } catch (error) { + logger.error("Error writing one time reference log: ", error); + } +} + +function readOfflineLog() { + fs.readFile(LOG_FILE_PATH, "utf-8", (err, data) => { + if (err) { + logger.error("Error reading offline log:", err); + } + + logger.debug("Returning data:", data); + return data; + }); +} + +module.exports = { + writeOfflineLog, + readOfflineLog, +}; From e2f3a9364d519b5ec075e6a94102acddfbb831cb Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 1 Nov 2024 19:20:20 +0100 Subject: [PATCH 002/135] Update build-dev.yaml --- .github/workflows/build-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index a8d55f2c..d3b13356 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -34,7 +34,7 @@ jobs: tags: | type=sha,format=long,prefix= flavor: | - type=schedule,pattern=nightly + type=pep440,pattern={{version}},value=nightly - name: Build and push uses: docker/build-push-action@v5 From 14c0951954286eb981cb4d5a25deba235542215f Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 1 Nov 2024 19:21:42 +0100 Subject: [PATCH 003/135] Update build-dev.yaml --- .github/workflows/build-dev.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index d3b13356..cce888e2 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -32,8 +32,6 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=sha,format=long,prefix= - flavor: | type=pep440,pattern={{version}},value=nightly - name: Build and push From f4d8ec10e4a045380da9dc9b51e21fbf97466d7a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 1 Nov 2024 19:25:41 +0100 Subject: [PATCH 004/135] Update build-dev.yaml --- .github/workflows/build-dev.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index cce888e2..9833ede7 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -32,7 +32,9 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=pep440,pattern={{version}},value=nightly + type=raw,enable=true,priority=200,prefix=,suffix=,value=nightly + flavor: | + latest=false - name: Build and push uses: docker/build-push-action@v5 From 5d6c61c52af3e67fc5f9bc6c12ec3bed41eb029f Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 1 Nov 2024 19:29:30 +0100 Subject: [PATCH 005/135] Add rate limiter --- server.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/server.js b/server.js index b7a2731c..9ccb80d9 100644 --- a/server.js +++ b/server.js @@ -21,11 +21,11 @@ swaggerDocs(app); scheduleFetch(); // Routes -app.use("/api", authMiddleware, api); -app.use("/conf", authMiddleware, conf); -app.use("/auth", authMiddleware, auth); -app.use("/data", authMiddleware, data); -app.use("/frontend", authMiddleware, frontend); +app.use("/api", authMiddleware, limiter, api); +app.use("/conf", authMiddleware, limiter, conf); +app.use("/auth", authMiddleware, limiter, auth); +app.use("/data", authMiddleware, limiter, data); +app.use("/frontend", authMiddleware, limiter, frontend); app.listen(PORT, () => { logger.info(`Server is running on http://localhost:${PORT}`); From 68bb589f6756a06081b05014e38aae0c830852df Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Fri, 1 Nov 2024 20:13:12 +0100 Subject: [PATCH 006/135] Move rate limiter to middleware --- {utils => middleware}/rateLimiter.js | 0 server.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {utils => middleware}/rateLimiter.js (100%) diff --git a/utils/rateLimiter.js b/middleware/rateLimiter.js similarity index 100% rename from utils/rateLimiter.js rename to middleware/rateLimiter.js diff --git a/server.js b/server.js index 9ccb80d9..f6a86f39 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ const authMiddleware = require("./middleware/authMiddleware"); const app = express(); const logger = require("./utils/logger"); const { scheduleFetch } = require("./controllers/scheduler"); -const { limiter } = require("./utils/rateLimiter"); +const { limiter } = require("./middleware/rateLimiter"); const PORT = "7070"; From 6123267a33b03976661090374e83399592e9f945 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Fri, 1 Nov 2024 21:02:36 +0100 Subject: [PATCH 007/135] Formating and details on swagger page --- config/db.js | 38 +++++++------- config/loggerConfig.js | 35 +++++++------ config/swaggerConfig.js | 57 ++++++++++----------- data/database.db | Bin 16384 -> 126976 bytes misc/dependencyGraphs/mermaid-all.txt | 70 +++++++++++++------------- package.json | 4 +- 6 files changed, 104 insertions(+), 100 deletions(-) diff --git a/config/db.js b/config/db.js index 9317ab40..51850d3e 100644 --- a/config/db.js +++ b/config/db.js @@ -1,19 +1,19 @@ -const sqlite3 = require('sqlite3').verbose(); -const logger = require('./../utils/logger'); -const path = require('path'); -const dbPath = path.join(__dirname, '../data/database.db'); - -const db = new sqlite3.Database(dbPath, (err) => { - if (err) { - logger.error('Error opening database:', err.message); - } else { - db.run(`CREATE TABLE IF NOT EXISTS data ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - info TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP - )`); - logger.info('Database created / opened succesfully'); - } -}); - -module.exports = db; \ No newline at end of file +const sqlite3 = require("sqlite3").verbose(); +const logger = require("./../utils/logger"); +const path = require("path"); +const dbPath = path.join(__dirname, "../data/database.db"); + +const db = new sqlite3.Database(dbPath, (err) => { + if (err) { + logger.error("Error opening database:", err.message); + } else { + db.run(`CREATE TABLE IF NOT EXISTS data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + info TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + )`); + logger.info("Database created / opened succesfully"); + } +}); + +module.exports = db; diff --git a/config/loggerConfig.js b/config/loggerConfig.js index 79503488..0f7641af 100644 --- a/config/loggerConfig.js +++ b/config/loggerConfig.js @@ -1,16 +1,19 @@ -const { format } = require('winston'); - -module.exports = { - level: 'info', - format: format.combine( - format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), - format.printf(({ timestamp, level, message }) => `${timestamp} [${level.toUpperCase()}]: ${message}`) - ), - transports: { - console: true, - file: { - enabled: true, - filename: 'logs/app.log', - }, - }, -}; +const { format } = require("winston"); + +module.exports = { + level: "info", + format: format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf( + ({ timestamp, level, message }) => + `${timestamp} [${level.toUpperCase()}]: ${message}`, + ), + ), + transports: { + console: true, + file: { + enabled: true, + filename: "logs/app.log", + }, + }, +}; diff --git a/config/swaggerConfig.js b/config/swaggerConfig.js index c79ae476..723897fc 100644 --- a/config/swaggerConfig.js +++ b/config/swaggerConfig.js @@ -1,28 +1,29 @@ -const options = { - definition: { - openapi: '3.0.0', - info: { - title: 'Your API Documentation', - version: '1.0.0', - description: 'API documentation with authentication', - }, - components: { - securitySchemes: { - passwordAuth: { - type: 'apiKey', - in: 'header', - name: 'x-password', - description: 'Password required for authentication', - }, - }, - }, - security: [ - { - passwordAuth: [], - }, - ], - }, - apis: ['./routes/*/*.js'], // Point to your route files -}; - -module.exports = options; +const options = { + definition: { + failOnErrors: true, + openapi: "3.0.0", + info: { + title: "DockStatAPI", + version: "2", + description: "An API used to query muliple docker hosts", + }, + components: { + securitySchemes: { + passwordAuth: { + type: "apiKey", + in: "header", + name: "x-password", + description: "Password required for authentication", + }, + }, + }, + security: [ + { + passwordAuth: [], + }, + ], + }, + apis: ["./routes/*/*.js"], +}; + +module.exports = options; diff --git a/data/database.db b/data/database.db index 80295980fc1caba1cff16d304197ec55893652f2..619beed4fb9bfcf112978dac7d78da14ad6617c9 100644 GIT binary patch literal 126976 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 delta 54 zcmZp8z~0cnI6<0~iGhKEWuk&TBh$u&1^k-=SOr-5zccWE-z=!`lz(CX2PZotvnXdu KVoBm60|5Y$9u8>$ diff --git a/misc/dependencyGraphs/mermaid-all.txt b/misc/dependencyGraphs/mermaid-all.txt index 87ba7da1..0fc2d66d 100644 --- a/misc/dependencyGraphs/mermaid-all.txt +++ b/misc/dependencyGraphs/mermaid-all.txt @@ -14,33 +14,33 @@ end subgraph 5["utils"] 6["dockerClient.js"] 8["containerService.js"] -N["extractHostData.js"] -O["writeOfflineLog.js"] -U["rateLimiter.js"] +O["extractHostData.js"] +P["writeOfflineLog.js"] end subgraph C["middleware"] D["authMiddleware.js"] +E["rateLimiter.js"] end -subgraph E["routes"] -subgraph F["auth"] -G["routes.js"] +subgraph F["routes"] +subgraph G["auth"] +H["routes.js"] end -subgraph H["data"] -I["routes.js"] +subgraph I["data"] +J["routes.js"] end -subgraph J["frontendController"] -K["routes.js"] +subgraph K["frontendController"] +L["routes.js"] end -subgraph L["getter"] -M["routes.js"] +subgraph M["getter"] +N["routes.js"] end -subgraph P["setter"] -Q["routes.js"] +subgraph Q["setter"] +R["routes.js"] end end -R["server.js"] -subgraph S["swagger"] -T["swaggerDocs.js"] +S["server.js"] +subgraph T["swagger"] +U["swaggerDocs.js"] end 4-->6 7-->1 @@ -49,22 +49,22 @@ end 8-->6 B-->1 B-->7 -I-->1 -K-->A -M-->9 -M-->B -M-->8 -M-->6 -M-->N -M-->O -Q-->B +J-->1 +L-->A +N-->9 +N-->B +N-->8 +N-->6 +N-->O +N-->P R-->B -R-->D -R-->G -R-->I -R-->K -R-->M -R-->Q -R-->T -R-->U -T-->2 +S-->B +S-->D +S-->E +S-->H +S-->J +S-->L +S-->N +S-->R +S-->U +U-->2 diff --git a/package.json b/package.json index bd38ba88..11077856 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dockstatapi", - "version": "1.0.0", + "version": "2", "description": "API for docker hosts using dockerode", "main": "server.js", "scripts": { @@ -11,7 +11,7 @@ }, "keywords": [], "author": "Its4Nik", - "license": "ISC", + "license": "BSD 3-Clause License", "dependencies": { "bcrypt": "^5.1.1", "child_process": "^1.0.2", From aa4a20fd5f4af1f24759754b09e52bd3053af205 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Sat, 2 Nov 2024 19:07:23 +0100 Subject: [PATCH 008/135] Added icon and url routes + adjustment of methods used when removinf information from /frontend/... --- controllers/frontendConfiguration.js | 118 ++++++++- data/database.db | Bin 126976 -> 577536 bytes routes/frontendController/routes.js | 347 +++++++++++++++++++++++---- server.js | 14 +- 4 files changed, 426 insertions(+), 53 deletions(-) diff --git a/controllers/frontendConfiguration.js b/controllers/frontendConfiguration.js index ff1ce3ea..2ba90e8c 100644 --- a/controllers/frontendConfiguration.js +++ b/controllers/frontendConfiguration.js @@ -2,7 +2,13 @@ const fs = require("fs"); const path = require("path"); const dataPath = path.join(__dirname, "../data/frontendConfiguration.json"); const logger = require("../utils/logger"); +const { PythonShellErrorWithLogs } = require("python-shell"); +const expression = + "https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)"; +const regex = new RegExp(expression); +/////////////////////////////////////////////////////////////// +// Hide Containers: async function hideContainer(containerName) { try { let data = await readData(); @@ -19,6 +25,7 @@ async function hideContainer(containerName) { } } catch (error) { logger.error(error); + throw new Error(error); } } @@ -36,9 +43,12 @@ async function unhideContainer(containerName) { } } catch (error) { logger.error(error); + throw new Error(error); } } +/////////////////////////////////////////////////////////////// +// Tag containers async function addTagToContainer(containerName, tag) { try { let data = await readData(); @@ -58,6 +68,7 @@ async function addTagToContainer(containerName, tag) { } } catch (error) { logger.error(error); + throw new Error(error); } } @@ -77,9 +88,12 @@ async function removeTagFromContainer(containerName, tag) { } } catch (error) { logger.error(error); + throw new Error(error); } } +/////////////////////////////////////////////////////////////// +// Pin containers async function pinContainer(containerName) { try { let data = await readData(); @@ -96,6 +110,7 @@ async function pinContainer(containerName) { } } catch (error) { logger.error(error); + throw new Error(error); } } @@ -113,9 +128,107 @@ async function unpinContainer(containerName) { } } catch (error) { logger.error(error); + throw new Error(error); } } +/////////////////////////////////////////////////////////////// +// Add/remove link from containers +async function setLink(containerName, link) { + if (link.match(regex)) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + data[containerIndex].link = `${link}`; + await saveData(data); + } else { + data.push({ name: containerName, link: `${link}` }); + await saveData(data); + } + } catch (error) { + logger.error(error); + throw new Error(error); + } + } else { + logger.error(`Provided link is not valid: ${link}`); + throw new Error(`Provided link is not valid: ${link}`); + } +} + +async function removeLink(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + delete data[containerIndex].link; + await saveData(data); + cleanupData(); + } + } catch (error) { + logger.error(error); + throw new Error(error); + } +} + +/////////////////////////////////////////////////////////////// +// Add/remove icon from containers +async function setIcon(containerName, icon, custom) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (custom === true) { + if (containerIndex !== -1) { + data[containerIndex].icon = `custom/${icon}`; + await saveData(data); + } else { + data.push({ name: containerName, icon: `custom/${icon}` }); + await saveData(data); + } + } else { + if (containerIndex !== -1) { + data[containerIndex].icon = `${icon}`; + await saveData(data); + } else { + data.push({ name: containerName, icon: `${icon}` }); + await saveData(data); + } + } + } catch (error) { + logger.error(error); + throw new Error(error); + } +} + +async function removeIcon(containerName) { + try { + let data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, + ); + + if (containerIndex !== -1) { + delete data[containerIndex].icon; + await saveData(data); + cleanupData(); + } + } catch (error) { + logger.error(error); + throw new Error(error); + } +} + +/////////////////////////////////////////////////////////////// +// Data specific functionss async function readData() { try { const data = await fs.promises.readFile(dataPath, "utf-8"); @@ -176,5 +289,8 @@ module.exports = { removeTagFromContainer, pinContainer, unpinContainer, - cleanupData, + setLink, + removeLink, + setIcon, + removeIcon, }; diff --git a/data/database.db b/data/database.db index 619beed4fb9bfcf112978dac7d78da14ad6617c9..6535b160318c34abbe5c6debfa6291df728eb2f9 100644 GIT binary patch literal 577536 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

?G{#>66I&Ykj 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 diff --git a/routes/frontendController/routes.js b/routes/frontendController/routes.js index 986276fa..ed4f127f 100644 --- a/routes/frontendController/routes.js +++ b/routes/frontendController/routes.js @@ -8,13 +8,25 @@ const { removeTagFromContainer, pinContainer, unpinContainer, + setLink, + removeLink, + setIcon, + removeIcon, } = require("../../controllers/frontendConfiguration"); +/* +____ ___ ____ _____ +| _ \ / _ \/ ___|_ _| +| |_) | | | \___ \ | | +| __/| |_| |___) || | +|_| \___/|____/ |_| +*/ + /** * @swagger - * /frontend/hide/{containerName}: + * /frontend/show/{containerName}: * post: - * summary: Hide a container + * summary: Unhide a container * tags: [Frontend Configuration] * parameters: * - in: path @@ -22,10 +34,10 @@ const { * schema: * type: string * required: true - * description: The name of the container to hide + * description: The name of the container to unhide * responses: * 200: - * description: Container hidden successfully. + * description: Container unhidden successfully. * content: * application/json: * schema: @@ -51,15 +63,70 @@ const { * type: string * description: Error message */ -// Hide a container -router.post("/hide/:containerName", async (req, res) => { +// Unhide a container +router.post("/show/:containerName", async (req, res) => { const { containerName } = req.params; - const target = containerName; - //console.log(target); + try { + await unhideContainer(containerName); + res.json({ success: true, message: "Container unhidden successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); +/** + * @swagger + * /frontend/tag/{containerName}/{tag}: + * post: + * summary: Add a tag to a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to add tag to + * - in: path + * name: tag + * schema: + * type: string + * required: true + * description: The tag to add + * responses: + * 200: + * description: Tag added successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Add a tag to a container +router.post("/tag/:containerName/:tag", async (req, res) => { + const { containerName, tag } = req.params; try { - await hideContainer(target); - res.json({ success: true, message: `Container, ${target}, hidden.` }); + await addTagToContainer(containerName, tag); + res.json({ success: true, message: "Tag added successfully." }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } @@ -67,9 +134,9 @@ router.post("/hide/:containerName", async (req, res) => { /** * @swagger - * /frontend/unhide/{containerName}: + * /frontend/pin/{containerName}: * post: - * summary: Unhide a container + * summary: Pin a container * tags: [Frontend Configuration] * parameters: * - in: path @@ -77,10 +144,10 @@ router.post("/hide/:containerName", async (req, res) => { * schema: * type: string * required: true - * description: The name of the container to unhide + * description: The name of the container to pin * responses: * 200: - * description: Container unhidden successfully. + * description: Container pinned successfully. * content: * application/json: * schema: @@ -106,12 +173,12 @@ router.post("/hide/:containerName", async (req, res) => { * type: string * description: Error message */ -// Unhide a container -router.post("/unhide/:containerName", async (req, res) => { +// Pin a container +router.post("/pin/:containerName", async (req, res) => { const { containerName } = req.params; try { - await unhideContainer(containerName); - res.json({ success: true, message: "Container unhidden successfully." }); + await pinContainer(containerName); + res.json({ success: true, message: "Container pinned successfully." }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } @@ -119,9 +186,9 @@ router.post("/unhide/:containerName", async (req, res) => { /** * @swagger - * /frontend/tag/{containerName}/{tag}: + * /frontend/add-link/{containerName}/{link}: * post: - * summary: Add a tag to a container + * summary: Add a link to a container * tags: [Frontend Configuration] * parameters: * - in: path @@ -129,16 +196,16 @@ router.post("/unhide/:containerName", async (req, res) => { * schema: * type: string * required: true - * description: The name of the container to add tag to + * description: The name of the container to add link to * - in: path - * name: tag + * name: link * schema: * type: string * required: true - * description: The tag to add + * description: The link to add * responses: * 200: - * description: Tag added successfully. + * description: Link added successfully. * content: * application/json: * schema: @@ -164,12 +231,12 @@ router.post("/unhide/:containerName", async (req, res) => { * type: string * description: Error message */ -// Add a tag to a container -router.post("/tag/:containerName/:tag", async (req, res) => { - const { containerName, tag } = req.params; +// Add link to container +router.post("/add-link/:containerName/:link", async (req, res) => { + const { containerName, link } = req.params; try { - await addTagToContainer(containerName, tag); - res.json({ success: true, message: "Tag added successfully." }); + await setLink(containerName, link); + res.json({ success: true, message: "Link added successfully." }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } @@ -177,8 +244,138 @@ router.post("/tag/:containerName/:tag", async (req, res) => { /** * @swagger - * /frontend/remove-tag/{containerName}/{tag}: + * /frontend/add-icon/{containerName}/{icon}/{useCustomIcon}: * post: + * summary: Add an Icon to a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to add link to + * - in: path + * name: icon + * schema: + * type: string + * required: true + * description: The Icon to add + * - in: path + * name: useCustomIcon + * shema: + * type: boolean + * required: false + * description: If this icon is a custom icon or nor + * responses: + * 200: + * description: Icon added successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Add Icon to container +router.post( + "/add-icon/:containerName/:icon/:useCustomIcon", + async (req, res) => { + const { containerName, icon, useCustomIcon } = req.params; + try { + const custom = useCustomIcon === "true"; + + await setIcon(containerName, icon, custom); + res.json({ success: true, message: "Icon added successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } + }, +); + +/* + ____ _____ _ _____ _____ _____ +| _ \| ____| | | ____|_ _| ____| +| | | | _| | | | _| | | | _| +| |_| | |___| |___| |___ | | | |___ +|____/|_____|_____|_____| |_| |_____| +*/ + +/** + * @swagger + * /frontend/hide/{containerName}: + * delete: + * summary: Hide a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to hide + * responses: + * 200: + * description: Container hidden successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Hide a container +router.delete("/hide/:containerName", async (req, res) => { + const { containerName } = req.params; + const target = containerName; + try { + await hideContainer(target); + res.json({ success: true, message: `Container, ${target}, hidden.` }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/remove-tag/{containerName}/{tag}: + * delete: * summary: Remove a tag from a container * tags: [Frontend Configuration] * parameters: @@ -223,7 +420,7 @@ router.post("/tag/:containerName/:tag", async (req, res) => { * description: Error message */ // Remove a tag from a container -router.post("/remove-tag/:containerName/:tag", async (req, res) => { +router.delete("/remove-tag/:containerName/:tag", async (req, res) => { const { containerName, tag } = req.params; try { await removeTagFromContainer(containerName, tag); @@ -235,9 +432,9 @@ router.post("/remove-tag/:containerName/:tag", async (req, res) => { /** * @swagger - * /frontend/pin/{containerName}: - * post: - * summary: Pin a container + * /frontend/unpin/{containerName}: + * delete: + * summary: Unpin a container * tags: [Frontend Configuration] * parameters: * - in: path @@ -245,10 +442,10 @@ router.post("/remove-tag/:containerName/:tag", async (req, res) => { * schema: * type: string * required: true - * description: The name of the container to pin + * description: The name of the container to unpin * responses: * 200: - * description: Container pinned successfully. + * description: Container unpinned successfully. * content: * application/json: * schema: @@ -274,12 +471,12 @@ router.post("/remove-tag/:containerName/:tag", async (req, res) => { * type: string * description: Error message */ -// Pin a container -router.post("/pin/:containerName", async (req, res) => { +// Unpin a container +router.delete("/unpin/:containerName", async (req, res) => { const { containerName } = req.params; try { - await pinContainer(containerName); - res.json({ success: true, message: "Container pinned successfully." }); + await unpinContainer(containerName); + res.json({ success: true, message: "Container unpinned successfully." }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } @@ -287,9 +484,9 @@ router.post("/pin/:containerName", async (req, res) => { /** * @swagger - * /frontend/unpin/{containerName}: - * post: - * summary: Unpin a container + * /frontend/remove-link/{containerName}: + * delete: + * summary: Remove a link from a container * tags: [Frontend Configuration] * parameters: * - in: path @@ -297,10 +494,10 @@ router.post("/pin/:containerName", async (req, res) => { * schema: * type: string * required: true - * description: The name of the container to unpin + * description: The name of the container to remove link from * responses: * 200: - * description: Container unpinned successfully. + * description: Link removed successfully. * content: * application/json: * schema: @@ -326,12 +523,64 @@ router.post("/pin/:containerName", async (req, res) => { * type: string * description: Error message */ -// Unpin a container -router.post("/unpin/:containerName", async (req, res) => { +// Remove link from container +router.delete("/remove-link/:containerName", async (req, res) => { const { containerName } = req.params; try { - await unpinContainer(containerName); - res.json({ success: true, message: "Container unpinned successfully." }); + await removeLink(containerName); + res.json({ success: true, message: "Link removed successfully." }); + } catch (error) { + res.status(500).json({ success: false, error: error.message }); + } +}); + +/** + * @swagger + * /frontend/remove-icon/{containerName}: + * delete: + * summary: Remove an icon from a container + * tags: [Frontend Configuration] + * parameters: + * - in: path + * name: containerName + * schema: + * type: string + * required: true + * description: The name of the container to remove the icon from + * responses: + * 200: + * description: Icon removed successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * error: + * type: string + * description: Error message + */ +// Remove icon from container +router.delete("/remove-icon/:containerName", async (req, res) => { + const { containerName } = req.params; + try { + await removeIcon(containerName); + res.json({ success: true, message: "Icon removed successfully." }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } diff --git a/server.js b/server.js index f6a86f39..bf289f60 100644 --- a/server.js +++ b/server.js @@ -1,16 +1,24 @@ const express = require("express"); +const app = express(); + +// Utility: const swaggerDocs = require("./swagger/swaggerDocs"); +const logger = require("./utils/logger"); + +// Routes: const api = require("./routes/getter/routes"); const conf = require("./routes/setter/routes"); const auth = require("./routes/auth/routes"); const data = require("./routes/data/routes"); const frontend = require("./routes/frontendController/routes"); + +// Middleware: const authMiddleware = require("./middleware/authMiddleware"); -const app = express(); -const logger = require("./utils/logger"); -const { scheduleFetch } = require("./controllers/scheduler"); const { limiter } = require("./middleware/rateLimiter"); +// Controllers +const { scheduleFetch } = require("./controllers/scheduler"); + const PORT = "7070"; app.use(express.json()); From 0bbfc91e6d981665e2e28bbc2ee71b4d23ef4bcd Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Sat, 2 Nov 2024 19:16:58 +0100 Subject: [PATCH 009/135] Use commonjs for rate limit --- data/database.db | Bin 577536 -> 585728 bytes middleware/rateLimiter.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data/database.db b/data/database.db index 6535b160318c34abbe5c6debfa6291df728eb2f9..caa9fbb0d7b4b30bfb4b2936cbb1e31f04e600b1 100644 GIT binary patch delta 1190 zcmZ8hO=w(I6rTI$&Ac15ah@}S4KdngtSE_(bMF70TC`vhTqz9!?IIm&5TqU2notpu zNx`8A+SEtAE?l%A7O@p#789f>U5nDL+_-QyZbU&Bu3UI#GRdTF-h=a<_ucQD@4K&a zt-pLC^2*Z6+p|q?m5|y>2-i)q z%AGcL&=NML9vOZDI#= zIeS-!TUVotkD{a&fcrEnYn)Twht@yQ6H{8!RDk0X3^?H=Lbzh0dOU#hwLj{7w|wvG zX#SI*^=T1 zKgLoe3@CxLUu}$4%I2#l?2Je2o%d#}YrsppHB!6K&!bvAXbJskK9!8>fqlk)D3KCW z;ofcjbX^*nd?zSUS~^I|eCGP#_Qhslt zROr7NS}(?5Rh*^uWn_XkU>zmz=%x5D3om_{Jhfj7w6Nz~(rplp%Ugd{V<0pfgkn7z zm3!<>{u_H(P$<0=84wxJrlk$P%$41gHC9-+*K9jKV4R3Rf5D_SjTTh~=bV%hgC#?W4 WLEe{mZP20C`Q3jT!yg2h@zH Date: Sat, 2 Nov 2024 19:27:48 +0100 Subject: [PATCH 010/135] Use ESM for rate limit; otherwise it wont work on my dev server (but locally?????) --- data/database.db | Bin 585728 -> 602112 bytes middleware/rateLimiter.js | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/data/database.db b/data/database.db index caa9fbb0d7b4b30bfb4b2936cbb1e31f04e600b1..ccc5cf7e392e87c9f58e2e912cedf258cbc1cd72 100644 GIT binary patch delta 2321 zcmZ9O-)|IE6vsQe^J@{>E<4*oTM*b51C+`A{iE0@Pxz)73Pc}ZLx`GywhF|Ei7m#) z5KziQt}kdXMuP!lX~Ggsgg%KN#wI|F8f9Na2>KUzW@fj$Yqxv1d%tI9&OPUQKev*)Lb_%v;-!plM`Hqs%nQP9u97In&6UM&1|5zjo?Trq`2%VM&=;J?<1ejwoi5 zo62=ZnDIxM$u1!%!y${uc&K*G_#;-gM2IOFs(6G!S6{s3Z?Y_5et*th;b5$Yh$fZ~ ztrQ$%#1+R{4zGE2?}1$h-dr<+rBaj=tj)wmKkyqdX4OTf=r_dZojdX*eawt+^C!)T zqgJOY5QhXzxyKt^K{XZ#zA4fwnydDgy;KQY+NVkjDYALTpwav*HS;A!hEZ9V}W9MP$8w)8xLWwcUuP{PjA?{* zD3DZ1Q>Yoe>gyuGQnuV%VoA7;Di;C=G9)<4Zz_7}Br$N$4d(t;zuUojgyGBAynUWE zX7+#YUCa}zC<%4)k&M;N&p0ENrOocU?$9!DoTi$H<|<%eigQzbkSV&UXy$T$Pd1+X zXOkt!!0GpSc8*LwE4*%CsCnBF4#Gp zCT8oo%ylDf`RjYA053Lk<6r^?EjS~x1s6&Uay0jR;4RE=bkCQ-CQvIDCiy{n?THD@ z6oQSBE%kHCz>o~J*p(0hULw-WJ@k8=L`}@sn#CSyE6{aUsJ7fd2b z%v7gULWD05_paf$)LA-DpzB4DoV zVL)db_n|E8p*}8QN1~P{Kq{09!IYUfWCc#5oB0ni0njs6%(d-yWjQPZe%fyPe-h;x zCQ=i|L!v0cO37;Nia!j9k1FpERENLum9>;g4nZnJ9B>{J2!QmhulguUIN0*x;*I~_MGibM{UfMx7g>p zA&iIMe>l?+ctNJ@$!eV(O(md@0H(_}G!UgkRQoIN8={$8=NGduYgXTM)GYeyYb#Hn z2jJxp%*wTbwwgcg`?_C&Tj20*Ax6v;c$`#G?MA@|79{~u<@La;D^dCH>*nbP{`w-| zyVZ&@R5&&B#)DX34X7y#QUN9Bmic*!J=qOg2F?N+0_PB Date: Tue, 5 Nov 2024 22:53:23 +0100 Subject: [PATCH 011/135] Telegram functionality and templating --- Dockerfile | 4 +- controllers/appriseController.js | 0 controllers/fetchData.js | 41 ++++++++++++++++- data/database.db | Bin 602112 -> 610304 bytes misc/entrypoint.sh | 0 package-lock.json | 17 +++++-- package.json | 9 ++++ routes/apprise/routes.js | 0 routes/frontendController/routes.js | 1 - utils/notifications/_test.js | 27 +++++++++++ utils/notifications/data/template.js | 61 +++++++++++++++++++++++++ utils/notifications/data/template.json | 3 ++ utils/notifications/mail.js | 26 +++++++++++ utils/notifications/telegram.js | 32 +++++++++++++ 14 files changed, 215 insertions(+), 6 deletions(-) create mode 100644 controllers/appriseController.js mode change 100644 => 100755 misc/entrypoint.sh create mode 100644 routes/apprise/routes.js create mode 100644 utils/notifications/_test.js create mode 100644 utils/notifications/data/template.js create mode 100644 utils/notifications/data/template.json create mode 100644 utils/notifications/mail.js create mode 100644 utils/notifications/telegram.js diff --git a/Dockerfile b/Dockerfile index 5fc294e6..b23d93c2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,9 @@ WORKDIR /api COPY --from=builder /api . -RUN apk add --no-cache bash curl +RUN apk add --no-cache \ + bash \ + curl EXPOSE 7070 diff --git a/controllers/appriseController.js b/controllers/appriseController.js new file mode 100644 index 00000000..e69de29b diff --git a/controllers/fetchData.js b/controllers/fetchData.js index 43b4f8a1..8df6b46f 100644 --- a/controllers/fetchData.js +++ b/controllers/fetchData.js @@ -3,6 +3,7 @@ const { fetchAllContainers } = require("../utils/containerService"); const logger = require("./../utils/logger"); const path = require("path"); const fs = require("fs"); +const { exec } = require("child_process"); const fetchData = async () => { try { @@ -12,7 +13,6 @@ const fetchData = async () => { if (process.env.OFFLINE === "true") { logger.info("No new data inserted --- OFFLINE MODE"); } else { - // Insert data into the SQLite database db.run( `INSERT INTO data (info) VALUES (?)`, [JSON.stringify(data)], @@ -26,6 +26,45 @@ const fetchData = async () => { }, ); } + + const containerStatus = {}; + Object.keys(allContainerData).forEach((host) => { + containerStatus[host] = allContainerData[host].map((container) => ({ + name: container.name, + id: container.id, + state: container.state, + host: container.hostName, + })); + }); + + const filePath = path.resolve(__dirname, "../data/states.json"); + let previousState = {}; + + if (fs.existsSync(filePath)) { + previousState = JSON.parse(fs.readFileSync(filePath, "utf8")); + } + + if (JSON.stringify(previousState) !== JSON.stringify(containerStatus)) { + fs.writeFileSync(filePath, JSON.stringify(containerStatus, null, 2)); + logger.info(`Container states saved to ${filePath}`); + + //TODO: rewrite every notification service using custom js modules + exec( + path.resolve(__dirname, "../misc/apprise.ppy"), + (error, stdout, stderr) => { + if (error) { + logger.error("Error executing apprise.py:", error.message); + return; + } + if (stderr) { + logger.warn("apprise.py stderr:", stderr); + } + logger.info("apprise.py executed successfully:", stdout); + }, + ); + } else { + logger.info("No state change detected, apprise.py not triggered."); + } } catch (error) { logger.error("Error fetching data:", error.message); } diff --git a/data/database.db b/data/database.db index ccc5cf7e392e87c9f58e2e912cedf258cbc1cd72..682c1a7450519549c99fb503d233e1e184c7bbb2 100644 GIT binary patch delta 32321 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 delta 21844 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 l;?0=ISI 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 M;dr<_z^Etx521eIv;Y7A diff --git a/misc/entrypoint.sh b/misc/entrypoint.sh old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json index 68d93740..1ee8b135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,13 +1,13 @@ { "name": "dockstatapi", - "version": "1.0.0", + "version": "2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dockstatapi", - "version": "1.0.0", - "license": "ISC", + "version": "2", + "license": "BSD 3-Clause License", "dependencies": { "bcrypt": "^5.1.1", "child_process": "^1.0.2", @@ -15,7 +15,9 @@ "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", + "js-yaml": "^4.1.0", "node-fetch": "^3.3.2", + "nodemailer": "^6.9.16", "python-shell": "^5.0.0", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", @@ -2566,6 +2568,15 @@ "node": ">= 10.12.0" } }, + "node_modules/nodemailer": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", diff --git a/package.json b/package.json index 11077856..c6f82f2e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,9 @@ "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", + "js-yaml": "^4.1.0", "node-fetch": "^3.3.2", + "nodemailer": "^6.9.16", "python-shell": "^5.0.0", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", @@ -30,5 +32,12 @@ "devDependencies": { "dependency-cruiser": "^16.5.0", "nodemon": "^3.1.7" + }, + "nodemonConfig": { + "ignore": [ + "**/logs/**", + "**/data/**" + ], + "delay": 2500 } } diff --git a/routes/apprise/routes.js b/routes/apprise/routes.js new file mode 100644 index 00000000..e69de29b diff --git a/routes/frontendController/routes.js b/routes/frontendController/routes.js index ed4f127f..de08c7a5 100644 --- a/routes/frontendController/routes.js +++ b/routes/frontendController/routes.js @@ -1,6 +1,5 @@ const express = require("express"); const router = express.Router(); -const logger = require("../../utils/logger"); const { hideContainer, unhideContainer, diff --git a/utils/notifications/_test.js b/utils/notifications/_test.js new file mode 100644 index 00000000..71398c7f --- /dev/null +++ b/utils/notifications/_test.js @@ -0,0 +1,27 @@ +const logger = require("../../utils/logger"); + +const { telegramNotification } = require("./telegram"); + +async function testNotification(type, containerId) { + if (!containerId) { + console.error("Container ID is required."); + return; + } + + switch (type) { + case "telegram": + logger.debug("Testing Telegram notification..."); + await telegramNotification(containerId); + break; + default: + logger.error("Unknown notification type. Use 'email' or 'telegram'."); + } +} + +if (require.main === module) { + const [type, containerId] = process.argv.slice(2); + testNotification(type, containerId); + console.log(`Testing ${type}, with: ${containerId}`); +} + +module.exports = testNotification; diff --git a/utils/notifications/data/template.js b/utils/notifications/data/template.js new file mode 100644 index 00000000..2bec652f --- /dev/null +++ b/utils/notifications/data/template.js @@ -0,0 +1,61 @@ +const fs = require("fs"); +const path = require("path"); + +const templatePath = path.join(__dirname, "template.json"); +const containersPath = path.join(__dirname, "../../../data/states.json"); + +function getTemplate() { + try { + const data = fs.readFileSync(templatePath, "utf8"); + return JSON.parse(data); + } catch (error) { + console.error("Failed to load template:", error); + return null; + } +} + +function setTemplate(newTemplate) { + try { + fs.writeFileSync( + templatePath, + JSON.stringify(newTemplate, null, 2), + "utf8", + ); + console.log("Template updated successfully"); + } catch (error) { + console.error("Failed to update template:", error); + } +} + +function renderTemplate(containerId) { + const template = getTemplate(); + if (!template) return null; + + try { + const data = fs.readFileSync(containersPath, "utf8"); + const containers = JSON.parse(data); + + let containerData = null; + for (const host in containers) { + containerData = containers[host].find((c) => c.id === containerId); + if (containerData) break; + } + + if (!containerData) { + console.error(`Container with ID ${containerId} not found`); + return null; + } + + // Substitute placeholders in the template with container data + return Object.keys(containerData).reduce( + (text, key) => + text.replace(new RegExp(`{{${key}}}`, "g"), containerData[key]), + template.text, + ); + } catch (error) { + console.error("Failed to load containers:", error); + return null; + } +} + +module.exports = { getTemplate, setTemplate, renderTemplate }; diff --git a/utils/notifications/data/template.json b/utils/notifications/data/template.json new file mode 100644 index 00000000..daa1f49d --- /dev/null +++ b/utils/notifications/data/template.json @@ -0,0 +1,3 @@ +{ + "text": "{{name}} ({{id}}) on {{host}} is {{state}}." +} diff --git a/utils/notifications/mail.js b/utils/notifications/mail.js new file mode 100644 index 00000000..24accb34 --- /dev/null +++ b/utils/notifications/mail.js @@ -0,0 +1,26 @@ +const nodemailer = require("nodemailer"); + +const transporter = nodemailer.createTransport({ + host: process.env.SMTP_SERVER_HOST, + port: process.env.SMTP_SERVER_PORT, + secure: process.env.SMTP_USE_SSL, + auth: { + user: process.env.SMTP_USER, + pass: process.env.SMTP_PASSWORD, + }, +}); + +const mailOptions = { + from: "yourusername@email.com", + to: "yourfriend@email.com", + subject: "Sending Email using Node.js", + text: "That was easy!", +}; + +transporter.sendMail(mailOptions, function (error, info) { + if (error) { + console.log("Error:", error); + } else { + console.log("Email sent:", info.response); + } +}); diff --git a/utils/notifications/telegram.js b/utils/notifications/telegram.js new file mode 100644 index 00000000..5c79bdc8 --- /dev/null +++ b/utils/notifications/telegram.js @@ -0,0 +1,32 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const telegram_bot_token = process.env.TELEGRAM_BOT_TOKEN; +const telegram_chat_id = process.env.TELEGRAM_CHAT_ID; + +export async function telegramNotification(containerId) { + const telegram_message = renderTemplate(containerId); + if (!telegram_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch( + `https://api.telegram.org/bot${telegram_bot_token}/sendMessage`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + chat_id: telegram_chat_id, + text: telegram_message, + }), + }, + ); + } catch (error) { + logger.error("Error sending message:", error); + } +} From 2860402ccf38260d26583711fe9dd1a6743eefae Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 6 Nov 2024 00:52:06 +0100 Subject: [PATCH 012/135] More notification services and routes for testing and configuring the template --- config/loggerConfig.js | 21 ++- controllers/appriseController.js | 0 controllers/databaseMigration.js | 20 +++ controllers/scheduler.js | 21 ++- data/database.db | Bin 610304 -> 610304 bytes package-lock.json | 218 +++++++++++++++++++++++++ package.json | 1 + routes/apprise/routes.js | 0 routes/auth/routes.js | 3 +- routes/notifications/routes.js | 159 ++++++++++++++++++ server.js | 2 + utils/logger.js | 12 +- utils/notifications/_notify.js | 59 +++++++ utils/notifications/_test.js | 27 --- utils/notifications/data/template.js | 9 +- utils/notifications/data/template.json | 2 +- utils/notifications/discord.js | 27 +++ utils/notifications/email.js | 36 ++++ utils/notifications/mail.js | 26 --- utils/notifications/pushbullet.js | 30 ++++ utils/notifications/pushover.js | 30 ++++ utils/notifications/slack.js | 27 +++ utils/notifications/whatsapp.js | 29 ++++ 23 files changed, 678 insertions(+), 81 deletions(-) delete mode 100644 controllers/appriseController.js create mode 100644 controllers/databaseMigration.js delete mode 100644 routes/apprise/routes.js create mode 100644 routes/notifications/routes.js create mode 100644 utils/notifications/_notify.js delete mode 100644 utils/notifications/_test.js create mode 100644 utils/notifications/discord.js create mode 100644 utils/notifications/email.js delete mode 100644 utils/notifications/mail.js create mode 100644 utils/notifications/pushbullet.js create mode 100644 utils/notifications/pushover.js create mode 100644 utils/notifications/slack.js create mode 100644 utils/notifications/whatsapp.js diff --git a/config/loggerConfig.js b/config/loggerConfig.js index 0f7641af..38149ec4 100644 --- a/config/loggerConfig.js +++ b/config/loggerConfig.js @@ -1,19 +1,18 @@ -const { format } = require("winston"); +const { createLogger, format, transports } = require("winston"); -module.exports = { +const logger = createLogger({ level: "info", format: format.combine( format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.printf( ({ timestamp, level, message }) => - `${timestamp} [${level.toUpperCase()}]: ${message}`, + `[${timestamp}] ${level.toUpperCase()}: ${message}`, ), ), - transports: { - console: true, - file: { - enabled: true, - filename: "logs/app.log", - }, - }, -}; + transports: [ + new transports.Console(), + new transports.File({ filename: "logs/app.log" }), + ], +}); + +module.exports = logger; diff --git a/controllers/appriseController.js b/controllers/appriseController.js deleted file mode 100644 index e69de29b..00000000 diff --git a/controllers/databaseMigration.js b/controllers/databaseMigration.js new file mode 100644 index 00000000..263de07f --- /dev/null +++ b/controllers/databaseMigration.js @@ -0,0 +1,20 @@ +const db = require("../config/db"); +const logger = require("../utils/logger"); + +function clearOldEntries() { + const twentyFourHoursAgo = Date.now() - 24 * 60 * 60 * 1000; + + db.run( + `DELETE FROM data WHERE createdAt < ?`, + [twentyFourHoursAgo], + (err) => { + if (err) { + logger.error("Error deleting old entries:", err.message); + throw new Error("Database cleanup failed"); + } + logger.info("Old entries cleared successfully"); + }, + ); +} + +module.exports = clearOldEntries; diff --git a/controllers/scheduler.js b/controllers/scheduler.js index 5bb3ca7b..6322d327 100644 --- a/controllers/scheduler.js +++ b/controllers/scheduler.js @@ -1,28 +1,41 @@ +// path: controllers/scheduler.js + const fetchData = require("./fetchData"); const logger = require("../utils/logger"); const db = require("../config/db"); -let fetchInterval = 5 * 60 * 1000; +let fetchInterval = 5 * 60 * 1000; // Fetch data every 5 minutes by default let intervalId; +let cleanupIntervalId; const scheduleFetch = () => { fetchData().then(() => { cleanupOldEntries(); }); + intervalId = setInterval(() => { logger.info( `Fetching data at interval of ${fetchInterval / 1000} seconds.`, ); - cleanupOldEntries(); fetchData(); }, fetchInterval); + + // Schedule cleanup every 24 hours (86400000 ms) + cleanupIntervalId = setInterval( + () => { + cleanupOldEntries(); + }, + 24 * 60 * 60 * 1000, + ); + logger.info(`Data fetching scheduled every ${fetchInterval / 1000} seconds.`); + logger.info("Old entries cleanup scheduled every 24 hours."); }; const setFetchInterval = (newInterval) => { if (intervalId) { clearInterval(intervalId); - logger.info(`Cleared existing fetch interval.`); + logger.info("Cleared existing fetch interval."); } fetchInterval = newInterval; scheduleFetch(); @@ -61,7 +74,7 @@ const cleanupOldEntries = async () => { ).toISOString(); try { await db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo); - logger.info(`Old entries cleared from the database.`); + logger.info("Old entries cleared from the database."); } catch (error) { logger.error(`Error clearing old entries: ${error.message}`); } diff --git a/data/database.db b/data/database.db index 682c1a7450519549c99fb503d233e1e184c7bbb2..d36f65d65fdc3cfd4736d30e65395b6a212c3393 100644 GIT binary patch delta 36079 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 delta 35527 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 diff --git a/package-lock.json b/package-lock.json index 1ee8b135..f4dcffbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", + "twilio": "^5.3.5", "winston": "^3.15.0", "yamljs": "^0.3.0" }, @@ -470,6 +471,23 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -641,6 +659,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/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": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", @@ -882,6 +906,18 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", @@ -974,6 +1010,12 @@ "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", @@ -1031,6 +1073,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1169,6 +1220,15 @@ "node": ">=6.0.0" } }, + "node_modules/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==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1477,6 +1537,40 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, + "node_modules/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==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -2112,6 +2206,49 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -2134,18 +2271,60 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "license": "MIT" }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/logform": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", @@ -2843,6 +3022,12 @@ "node": ">= 0.10" } }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3069,6 +3254,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -3726,6 +3917,24 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, + "node_modules/twilio": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.3.5.tgz", + "integrity": "sha512-f/sA1Yd6TyIzfcq0u4QDGU+93afwswsJB+rf3T08tvBAMobBDVR3DfGREwJr5jp8xUic0qWa7GbJidk16NA4bg==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.2", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3911,6 +4120,15 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index c6f82f2e..b1c3b7c4 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", + "twilio": "^5.3.5", "winston": "^3.15.0", "yamljs": "^0.3.0" }, diff --git a/routes/apprise/routes.js b/routes/apprise/routes.js deleted file mode 100644 index e69de29b..00000000 diff --git a/routes/auth/routes.js b/routes/auth/routes.js index a0b217e4..bbc20d4b 100644 --- a/routes/auth/routes.js +++ b/routes/auth/routes.js @@ -83,8 +83,9 @@ router.post("/enable", (req, res) => { const passwordData = { hash, salt }; fs.writeFile(passwordFile, JSON.stringify(passwordData), (err) => { - if (err) + if (err) { return res.status(500).json({ message: "Error saving password" }); + } setTrue(); res.json({ message: "Authentication enabled" }); }); diff --git a/routes/notifications/routes.js b/routes/notifications/routes.js new file mode 100644 index 00000000..592ab638 --- /dev/null +++ b/routes/notifications/routes.js @@ -0,0 +1,159 @@ +const express = require("express"); +const router = express.Router(); +const logger = require("../../utils/logger"); +const path = require("path"); +const fs = require("fs"); +const notify = require("../../utils/notifications/_notify"); +const dataTemplate = path.join( + __dirname, + "../../utils/notifications/data/template.json", +); +/** + * @swagger + * /notification-service/get-template: + * get: + * summary: Retrieve the notification template + * tags: [Notification Service] + * responses: + * 200: + * description: Template data retrieved successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * description: Indicates if the operation was successful + * data: + * type: object + * description: The template data in JSON format + * 500: + * description: Internal server error. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message + */ +router.get("/get-template", (req, res) => { + fs.readFile(dataTemplate, "utf-8", (error, data) => { + if (error) { + logger.error("Errored opening:", error); + return res.status(500).json({ message: `Error opening: ${error}` }); + } + res.json(JSON.parse(data)); + }); +}); + +/** + * @swagger + * /notification-service/set-template: + * post: + * summary: Update the notification template + * tags: [Notification Service] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * description: New template data to save + * responses: + * 200: + * description: Template updated successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Success message + * 500: + * description: Internal server error. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Error message + */ +router.post("/set-template", (req, res) => { + const newData = req.body; + + fs.writeFile( + dataTemplate, + JSON.stringify(newData, null, 2), + "utf-8", + (error) => { + if (error) { + logger.error("Errored writing to file:", error); + return res + .status(500) + .json({ message: `Error writing to file: ${error}` }); + } + res.json({ message: "Template updated successfully." }); + }, + ); +}); + +/** + * @swagger + * /notification-service/test/{type}/{containerId}: + * post: + * summary: Send a test notification for a specific container + * tags: [Notification Service] + * parameters: + * - in: path + * name: type + * schema: + * type: string + * required: true + * description: Type of notification to test + * - in: path + * name: containerId + * schema: + * type: string + * required: true + * description: The ID of the container for the notification test + * responses: + * 200: + * description: Test notification sent successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * message: + * type: string + * 500: + * description: Internal server error. + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * message: + * type: string + */ +router.post("/test/:type/:containerId", async (req, res) => { + const { type, containerId } = req.params; + try { + await notify(type, containerId); + res.json({ success: true, message: `Sent test notification to ${type}` }); + } catch (error) { + res.json({ success: false, message: `Errored: ${error}` }); + } +}); + +module.exports = router; diff --git a/server.js b/server.js index bf289f60..a62b625d 100644 --- a/server.js +++ b/server.js @@ -11,6 +11,7 @@ const conf = require("./routes/setter/routes"); const auth = require("./routes/auth/routes"); const data = require("./routes/data/routes"); const frontend = require("./routes/frontendController/routes"); +const notificationService = require("./routes/notifications/routes"); // Middleware: const authMiddleware = require("./middleware/authMiddleware"); @@ -34,6 +35,7 @@ app.use("/conf", authMiddleware, limiter, conf); app.use("/auth", authMiddleware, limiter, auth); app.use("/data", authMiddleware, limiter, data); app.use("/frontend", authMiddleware, limiter, frontend); +app.use("/notification-service", authMiddleware, limiter, notificationService); app.listen(PORT, () => { logger.info(`Server is running on http://localhost:${PORT}`); diff --git a/utils/logger.js b/utils/logger.js index 853ca6fc..9d25e5d6 100644 --- a/utils/logger.js +++ b/utils/logger.js @@ -3,13 +3,11 @@ const loggerConfig = require("../config/loggerConfig"); const transports = [new winston.transports.Console()]; -if (loggerConfig.transports.file.enabled) { - transports.push( - new winston.transports.File({ - filename: loggerConfig.transports.file.filename, - }), - ); -} +transports.push( + new winston.transports.File({ + filename: "./logs/app.log", + }), +); const logger = winston.createLogger({ level: loggerConfig.level, diff --git a/utils/notifications/_notify.js b/utils/notifications/_notify.js new file mode 100644 index 00000000..b4a96fda --- /dev/null +++ b/utils/notifications/_notify.js @@ -0,0 +1,59 @@ +const logger = require("../../utils/logger"); + +const { telegramNotification } = require("./telegram"); +const { slackNotification } = require("./slack"); +const { discordNotification } = require("./discord"); +const { emailNotification } = require("./email"); +const { whatsappNotification } = require("./whatsapp"); +const { pushbulletNotification } = require("./pushbullet"); +const { pushoverNotification } = require("./pushover"); + +async function notify(type, containerId) { + if (!containerId) { + logger.error("Container ID is required."); + throw new Error("Container ID is required."); + } + + switch (type) { + case "telegram": + logger.debug("Testing Telegram notification..."); + await telegramNotification(containerId); + break; + case "slack": + logger.debug("Testing Slack notification..."); + await slackNotification(containerId); + break; + case "discord": + logger.debug("Testing Discord notification..."); + await discordNotification(containerId); + break; + case "email": + logger.debug("Testing Email notification..."); + await emailNotification(containerId); + break; + case "whatsapp": + logger.debug("Testing WhatsApp notification..."); + await whatsappNotification(containerId); + break; + case "pushbullet": + logger.debug("Testing Pushbullet notification..."); + await pushbulletNotification(containerId); + break; + case "pushover": + logger.debug("Testing Pushover notification..."); + await pushoverNotification(containerId); + break; + default: + const errorMsg = "Unknown notification type."; + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +if (require.main === module) { + const [type, containerId] = process.argv.slice(2); + notify(type, containerId); + console.log(`Testing ${type}, with: ${containerId}`); +} + +module.exports = notify; diff --git a/utils/notifications/_test.js b/utils/notifications/_test.js deleted file mode 100644 index 71398c7f..00000000 --- a/utils/notifications/_test.js +++ /dev/null @@ -1,27 +0,0 @@ -const logger = require("../../utils/logger"); - -const { telegramNotification } = require("./telegram"); - -async function testNotification(type, containerId) { - if (!containerId) { - console.error("Container ID is required."); - return; - } - - switch (type) { - case "telegram": - logger.debug("Testing Telegram notification..."); - await telegramNotification(containerId); - break; - default: - logger.error("Unknown notification type. Use 'email' or 'telegram'."); - } -} - -if (require.main === module) { - const [type, containerId] = process.argv.slice(2); - testNotification(type, containerId); - console.log(`Testing ${type}, with: ${containerId}`); -} - -module.exports = testNotification; diff --git a/utils/notifications/data/template.js b/utils/notifications/data/template.js index 2bec652f..9a090f61 100644 --- a/utils/notifications/data/template.js +++ b/utils/notifications/data/template.js @@ -1,5 +1,6 @@ const fs = require("fs"); const path = require("path"); +const logger = require("../../logger"); const templatePath = path.join(__dirname, "template.json"); const containersPath = path.join(__dirname, "../../../data/states.json"); @@ -21,9 +22,9 @@ function setTemplate(newTemplate) { JSON.stringify(newTemplate, null, 2), "utf8", ); - console.log("Template updated successfully"); + logger.log("Template updated successfully"); } catch (error) { - console.error("Failed to update template:", error); + logger.error("Failed to update template:", error); } } @@ -50,10 +51,10 @@ function renderTemplate(containerId) { return Object.keys(containerData).reduce( (text, key) => text.replace(new RegExp(`{{${key}}}`, "g"), containerData[key]), - template.text, + template.message, ); } catch (error) { - console.error("Failed to load containers:", error); + logger.error("Failed to load containers:", error); return null; } } diff --git a/utils/notifications/data/template.json b/utils/notifications/data/template.json index daa1f49d..6a57d442 100644 --- a/utils/notifications/data/template.json +++ b/utils/notifications/data/template.json @@ -1,3 +1,3 @@ { "text": "{{name}} ({{id}}) on {{host}} is {{state}}." -} +} \ No newline at end of file diff --git a/utils/notifications/discord.js b/utils/notifications/discord.js new file mode 100644 index 00000000..c7bfe828 --- /dev/null +++ b/utils/notifications/discord.js @@ -0,0 +1,27 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const discord_webhook_url = process.env.DISCORD_WEBHOOK_URL; + +export async function discordNotification(containerId) { + const discord_message = renderTemplate(containerId); + if (!discord_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch(discord_webhook_url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + content: discord_message, + }), + }); + } catch (error) { + logger.error("Error sending Discord message:", error); + } +} diff --git a/utils/notifications/email.js b/utils/notifications/email.js new file mode 100644 index 00000000..d7016795 --- /dev/null +++ b/utils/notifications/email.js @@ -0,0 +1,36 @@ +import nodemailer from "nodemailer"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const email_sender = process.env.EMAIL_SENDER; +const email_recipient = process.env.EMAIL_RECIPIENT; +const email_password = process.env.EMAIL_PASSWORD; + +export async function emailNotification(containerId) { + const email_message = renderTemplate(containerId); + if (!email_message) { + logger.error("Failed to create notification message."); + return; + } + + const transporter = nodemailer.createTransport({ + service: "gmail", + auth: { + user: email_sender, + pass: email_password, + }, + }); + + const mailOptions = { + from: email_sender, + to: email_recipient, + subject: "Container Notification", + text: email_message, + }; + + try { + await transporter.sendMail(mailOptions); + } catch (error) { + logger.error("Error sending email:", error); + } +} diff --git a/utils/notifications/mail.js b/utils/notifications/mail.js deleted file mode 100644 index 24accb34..00000000 --- a/utils/notifications/mail.js +++ /dev/null @@ -1,26 +0,0 @@ -const nodemailer = require("nodemailer"); - -const transporter = nodemailer.createTransport({ - host: process.env.SMTP_SERVER_HOST, - port: process.env.SMTP_SERVER_PORT, - secure: process.env.SMTP_USE_SSL, - auth: { - user: process.env.SMTP_USER, - pass: process.env.SMTP_PASSWORD, - }, -}); - -const mailOptions = { - from: "yourusername@email.com", - to: "yourfriend@email.com", - subject: "Sending Email using Node.js", - text: "That was easy!", -}; - -transporter.sendMail(mailOptions, function (error, info) { - if (error) { - console.log("Error:", error); - } else { - console.log("Email sent:", info.response); - } -}); diff --git a/utils/notifications/pushbullet.js b/utils/notifications/pushbullet.js new file mode 100644 index 00000000..442f44d0 --- /dev/null +++ b/utils/notifications/pushbullet.js @@ -0,0 +1,30 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const pushbullet_access_token = process.env.PUSHBULLET_ACCESS_TOKEN; + +export async function pushbulletNotification(containerId) { + const pushbullet_message = renderTemplate(containerId); + if (!pushbullet_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch("https://api.pushbullet.com/v2/pushes", { + method: "POST", + headers: { + "Access-Token": pushbullet_access_token, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + type: "note", + title: "Container Notification", + body: pushbullet_message, + }), + }); + } catch (error) { + logger.error("Error sending Pushbullet message:", error); + } +} diff --git a/utils/notifications/pushover.js b/utils/notifications/pushover.js new file mode 100644 index 00000000..592e7f09 --- /dev/null +++ b/utils/notifications/pushover.js @@ -0,0 +1,30 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const pushover_user_key = process.env.PUSHOVER_USER_KEY; +const pushover_api_token = process.env.PUSHOVER_API_TOKEN; + +export async function pushoverNotification(containerId) { + const pushover_message = renderTemplate(containerId); + if (!pushover_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch("https://api.pushover.net/1/messages.json", { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams({ + token: pushover_api_token, + user: pushover_user_key, + message: pushover_message, + }), + }); + } catch (error) { + logger.error("Error sending Pushover message:", error); + } +} diff --git a/utils/notifications/slack.js b/utils/notifications/slack.js new file mode 100644 index 00000000..2c1a67a2 --- /dev/null +++ b/utils/notifications/slack.js @@ -0,0 +1,27 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const slack_webhook_url = process.env.SLACK_WEBHOOK_URL; + +export async function slackNotification(containerId) { + const slack_message = renderTemplate(containerId); + if (!slack_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch(slack_webhook_url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + text: slack_message, + }), + }); + } catch (error) { + logger.error("Error sending Slack message:", error); + } +} diff --git a/utils/notifications/whatsapp.js b/utils/notifications/whatsapp.js new file mode 100644 index 00000000..d714b0b6 --- /dev/null +++ b/utils/notifications/whatsapp.js @@ -0,0 +1,29 @@ +import fetch from "node-fetch"; +import logger from "../logger.js"; +import { renderTemplate } from "./data/template.js"; + +const whatsapp_api_url = process.env.WHATSAPP_API_URL; // e.g., Twilio or other API service +const whatsapp_recipient = process.env.WHATSAPP_RECIPIENT; + +export async function whatsappNotification(containerId) { + const whatsapp_message = renderTemplate(containerId); + if (!whatsapp_message) { + logger.error("Failed to create notification message."); + return; + } + + try { + await fetch(whatsapp_api_url, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + to: whatsapp_recipient, + body: whatsapp_message, + }), + }); + } catch (error) { + logger.error("Error sending WhatsApp message:", error); + } +} From b8f501e0fd425e777aeaf7ad07de073dcd31a66c Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 6 Nov 2024 01:14:45 +0100 Subject: [PATCH 013/135] Fix CodeQL --- controllers/fetchData.js | 18 ++---------------- controllers/frontendConfiguration.js | 3 +-- controllers/scheduler.js | 6 +----- data/database.db | Bin 610304 -> 610304 bytes server.js | 12 ++++++------ 5 files changed, 10 insertions(+), 29 deletions(-) diff --git a/controllers/fetchData.js b/controllers/fetchData.js index 8df6b46f..ba14c348 100644 --- a/controllers/fetchData.js +++ b/controllers/fetchData.js @@ -47,23 +47,9 @@ const fetchData = async () => { if (JSON.stringify(previousState) !== JSON.stringify(containerStatus)) { fs.writeFileSync(filePath, JSON.stringify(containerStatus, null, 2)); logger.info(`Container states saved to ${filePath}`); - - //TODO: rewrite every notification service using custom js modules - exec( - path.resolve(__dirname, "../misc/apprise.ppy"), - (error, stdout, stderr) => { - if (error) { - logger.error("Error executing apprise.py:", error.message); - return; - } - if (stderr) { - logger.warn("apprise.py stderr:", stderr); - } - logger.info("apprise.py executed successfully:", stdout); - }, - ); + //TODO: logic + notification levels per service } else { - logger.info("No state change detected, apprise.py not triggered."); + logger.info("No state change detected, notifications not triggered."); } } catch (error) { logger.error("Error fetching data:", error.message); diff --git a/controllers/frontendConfiguration.js b/controllers/frontendConfiguration.js index 2ba90e8c..cdbee131 100644 --- a/controllers/frontendConfiguration.js +++ b/controllers/frontendConfiguration.js @@ -2,9 +2,8 @@ const fs = require("fs"); const path = require("path"); const dataPath = path.join(__dirname, "../data/frontendConfiguration.json"); const logger = require("../utils/logger"); -const { PythonShellErrorWithLogs } = require("python-shell"); const expression = - "https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)"; + "https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}([-a-zA-Z0-9()@:%_+.~#?&//=]*)"; const regex = new RegExp(expression); /////////////////////////////////////////////////////////////// diff --git a/controllers/scheduler.js b/controllers/scheduler.js index 6322d327..e19b17eb 100644 --- a/controllers/scheduler.js +++ b/controllers/scheduler.js @@ -1,12 +1,10 @@ -// path: controllers/scheduler.js - const fetchData = require("./fetchData"); const logger = require("../utils/logger"); const db = require("../config/db"); +const regex = /(\d{1,5})([smh])/g; let fetchInterval = 5 * 60 * 1000; // Fetch data every 5 minutes by default let intervalId; -let cleanupIntervalId; const scheduleFetch = () => { fetchData().then(() => { @@ -20,7 +18,6 @@ const scheduleFetch = () => { fetchData(); }, fetchInterval); - // Schedule cleanup every 24 hours (86400000 ms) cleanupIntervalId = setInterval( () => { cleanupOldEntries(); @@ -50,7 +47,6 @@ const parseInterval = (interval) => { }; let totalMilliseconds = 0; - const regex = /(\d+)([smh])/g; let match; while ((match = regex.exec(interval))) { diff --git a/data/database.db b/data/database.db index d36f65d65fdc3cfd4736d30e65395b6a212c3393..400a0e0e09b55b0717a56b00a295397aa0b270ba 100644 GIT binary patch delta 19067 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 JMCiqq{|}7$b8P?s diff --git a/server.js b/server.js index a62b625d..35350977 100644 --- a/server.js +++ b/server.js @@ -30,12 +30,12 @@ swaggerDocs(app); scheduleFetch(); // Routes -app.use("/api", authMiddleware, limiter, api); -app.use("/conf", authMiddleware, limiter, conf); -app.use("/auth", authMiddleware, limiter, auth); -app.use("/data", authMiddleware, limiter, data); -app.use("/frontend", authMiddleware, limiter, frontend); -app.use("/notification-service", authMiddleware, limiter, notificationService); +app.use("/api", limiter, authMiddleware, api); +app.use("/conf", limiter, authMiddleware, conf); +app.use("/auth", limiter, authMiddleware, auth); +app.use("/data", limiter, authMiddleware, data); +app.use("/frontend", limiter, authMiddleware, frontend); +app.use("/notification-service", limiter, authMiddleware, notificationService); app.listen(PORT, () => { logger.info(`Server is running on http://localhost:${PORT}`); From 367bda08be3683126fbcca6a24d0e18e6e096d74 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 6 Nov 2024 01:34:47 +0100 Subject: [PATCH 014/135] Update mermaid diagramm for documentation purposes --- data/database.db | Bin 610304 -> 610304 bytes misc/dependencyGraphs/mermaid-all.txt | 124 +++++++++++------- misc/dependencyGraphs/mermaid-api.txt | 20 +-- misc/dependencyGraphs/mermaid-conf.txt | 16 ++- .../mermaid-notificationService.txt | 37 ++++++ 5 files changed, 137 insertions(+), 60 deletions(-) create mode 100644 misc/dependencyGraphs/mermaid-notificationService.txt diff --git a/data/database.db b/data/database.db index 400a0e0e09b55b0717a56b00a295397aa0b270ba..d916b79f6501db2292480baf31d83d3c2cb38163 100644 GIT binary patch delta 3699 zcmZ`+OKepc!ZIjqDAxWLfc|TI1%>wa~NFvfM2x{y_!~zn+S7)OEupd?MTtT(pt6WJNGWQukw_PG(Xax6M1o3{MNs+99gkKm_J-uBgU@X9%Ig9ORV=s0Fycxm8nfKk9GVuTn` zbKq`#|Kp7(j-5Gr<~tMnC{>0ED+G5}ZxzQP!-VASU?mZycN0$0##gap`7m1(VH-vuQDyVtn_!* zxI>52O>qu!H(yC^7P+ME^*_eriQ-&yPTkB$g~^Bg?L-UM(b-hs;96=X+M1(nmE)sSl?n(ip}iv+LX~h_c$vJX@G-PCu6n1;co>|I6~L zzqn_9n&^i(5iqMe5Q;D=g}}VKvnLoSArh@MjjN|dN^59i@j2x_^?vgGM)|S9kV{3} z$s=KHTvMcqAjXJJ7`R{*v1*)rvx4nbaW^|0jijrPcJ#wQy5~0(UW;)ct4w3&*~#Xo z2MfbN5s8Qp1e2R^uOyV(&dwSEhg+ti(YOV=rNLq)$)}lW%>3g~_xF#Za>VRDPJfk# z$d;cn@AT1nf8t3MrTH+Qm7 zuW3$<$^^Aq`fuw3ypCUSA*dpt+SrUSO_c(9;IJ-6b`$Ku#v}}COiC`41S3`cg|A~I z6#}_BdnBxGL(H_bLgVyZ)_`G332!eQ2uG6DMefe?;aETIfSzMjB?Z(a!&rUy98Pg6 zxNXp{O+Ml|MWuxsN#{|XS)BS5T2#eKX>EyHq6x^+27})gTf8eM)!27?TS#zAWBCEd z28sv?rgZkS#`Diucj!zq68Z&h@#W%(yOIWBvI@F8Un%bW$+u55-+nJngPO$YW#%ud zqu0fZKr@L84#!ou)t3HUH4L(qL4*)qo5liSTfe)2C^w*+t|G5=^ShdD7%Q?vyFabyP zgiRxk)Hd(+r@MlxMObkJn*GnTM$TdD#_YeUZu)Y3zl!3jA!a;N8g>yc4P6XHenJM^ z%#(3-Gs4SlcWFStQSe%p#Vm*O$`Iv%qeE%tmN$fR1uNXmcaus1YCVme#!(q9VbIG? z3UNgVDm`;crC=m&FK~+~{#(Q7fzq#QAh&^>WQyR4_||)1$cDAh>H1(WxrY*#cMOJM z5n7Hpj6T|1MU+G#fTy2Kt8U?MVK`1P0pfLz1_waJlrP&}Q%cxX=ZoA@R;=`Q!B7=+ zB6kaOgOWi$G70TNNmb3KX5+_#GD8w^DznvD(_B22ekQ2F4CK8wnIuAKW+44t5kN4l zl_+ya?p*V@FEc&ogRDU=kz|e8H}hJi^EIo-TSI;Mx#h@L9nUs3fjy> zB7y=I)P!GCcs*Z`*WflJn)JPRF(`x9R%F1rKQGbEJNKihU|12nGJk2kXmhB)@VTHY z4SdOLhT-361l~%_R!c2XvrEIlOsKInq_Kazl4=TV!V0&vJA|OOF<1X)+-U<6F13)_ zwlxg9cMQrk%$M@qb2zha6HvTcY3yYTARH%~P_d<;z=eBlEPcp*>1eul)XMHCK((kw z_vv?vuoEiU)u=G)b<59V6X@2_ba)U|pKU;12g&um1HGhg2=fXAxoY8!mCcnX2i*MK$cvRLV--k< z!&npu=s@$%g~SG7E<`)NobC(INUX_B4eb^(G()GM&=$7{)|KVxhfrW${hfH{m_*Cc zRTt1SK$^JQ|ND3(X6Qu~0)+k~X#@9;CznA=92Grc<}RK_oqFp;vS$l=WvF&{cofla zgYZ1@8k6IBa7ug?(r(9()8?2$#V{hXL@I^;7peyjpSy7|jmzP3U$P80{|;`h)<@vx zTF1>>Q3ws%Y2;X!b!d#xo=@M7e1T9#qd=kTwpjp=qbixaKqRX;JP}k-A3Wj~uO!-$ z-Ki@>tZ0T--?u*KJ$TFK*S0_Q*|xWv5cN{vUU(Bg2J+x%lts4p7=s@`Cbz}QI6>HYWt>2Xp!aPV&llt_PT*pX cIdY)%n+ooPq~f<4H4t(o;ocYDIGKL%Um?K6zyJUM delta 3213 zcmZ8jYlvM}8J%+D_XYSlbN@gaRSK@2txZjU4BJ(2@5j8<7QjsLkplB0kY?4-t zb|x0vLe(@E*;4;BAU+_WDXF;y1#|xy(`W|bFa6Q^C8A)W5lXGrwa+;-_e>eiIm21s zJ$vu9*IM6ReR<#N%lpoc@AN}MLqEhdh3nLdA25-B_2^GVk_SgF;l2*e z?`k^w_{i8}Pvh;8Z{zmpw{Uyp?9kOqdp|oGT{uUt(TH5HKY_Q;JiFs=gsXz9imQgJ z-rI3Ee(T+_nG?rP969~Ou`eB;S=5R$YCXGE-M5EwB?Y%m+xeN3$4?zO_0-HFV_Gq1 ztx*1=t~MWFTSb23%cM0tfYKoYb#Nu>@nw1zQ> zyu|naUGKyN6#crY{OIT6a}6hy)jD-n2*Vv`#(%0)HM^zeR!gVo;KR;1%c%1G%cMmL z-1k}1N(#jKd$;2G3s0Wx{mLXs)R2L`)F^>Lm1W%aHhx4Fq6QPx$`ph#3?R5+y}fV8 z2crhp&KPUc{Gpxor+=K-ae>XTh7TqJW)v3=<=vIb&BQRCmH73`N$Y_D=%YW!H>(ar z9PL!+CJYADTxwVPl8_vlQo{HDMA~tV`~EMnq%9;c)%)gu*Hhiqcg|Ee3me7CYXqNrvv<=KNP7S!xOs+cKmSEu$Z@$CN)l3F0?k#E`fHH@c8!66HuEl1sI$f|-HgMoWa>4>UreyR z0roRElWGW z_Rr#{XM-LsY@Ql8T{si$QQB-qrR}ax?%OR8e9GBvag7;krv0lwk6WaGLr}Nt#rxmZ zouB$d+-)Eu1Vbod zcxctVvPbVm%t?WCDp9K(xWN{!EYzDMLL-2r1ZT1qs*v<-^8|23P^m(#>962lCgu&p!_L3Qa-N5jzVc0&e@k=jM+sP)_ zUHhZ*%jaurqevlXQ_!2`DicK;gFY#y#xWX3K+G3yj62GWW)21o4~oPoZy6I(AZ@$r zXG0rv=yf2yG&`gL5(OKY=?>wW7j>`vzMc260$urJ{ITxxoeMPxn)$UG^|{Glc&Ec^ zODPvDEt%u~@QGS$c&j99jG5m9$UVCsWrqLGoVDn_Iz>y+2HCKE`qJQY!gX)I6GA{h ze^~fn_RdnyIpx_20X-}$@Jo+ZTcm*e?N3)0wp!RIe$H6Rw6G3J*RHqmR=g06ffK1= zc1vMz>@Qkd*1~4Ev<7*LUvMN)#$1V9Bb4^{4prv%pl3-c3_LfOT!;oM!4S_p8TwjQ z}e^Z z;D18}!T?E`H8zdZ{9vsWf*}Ci=xiPg4>|8_ zyulHS&((Bq=U2!})ec;sN*)WTU^vTqOpO14`g^;gW)>F#z(MBH7!x23QpYh&TO^7s z@kpAW80&#C`4kvC zSYixP1J6dJd8)qWq)9_}DLkOF#YhN}Cj@KirDj*hpwr`t|3M zTmId@LzzvKkA5xr T-uskb62}QJ_uZc^CvW~A!)8p4 diff --git a/misc/dependencyGraphs/mermaid-all.txt b/misc/dependencyGraphs/mermaid-all.txt index 0fc2d66d..6036bdd7 100644 --- a/misc/dependencyGraphs/mermaid-all.txt +++ b/misc/dependencyGraphs/mermaid-all.txt @@ -3,68 +3,104 @@ flowchart LR subgraph 0["config"] 1["db.js"] 2["swaggerConfig.js"] -9["dockerConfig.json"] +B["dockerConfig.json"] end subgraph 3["controllers"] 4["containerController.js"] -7["fetchData.js"] -A["frontendConfiguration.js"] -B["scheduler.js"] +7["databaseMigration.js"] +8["fetchData.js"] +C["frontendConfiguration.js"] +D["scheduler.js"] end subgraph 5["utils"] 6["dockerClient.js"] -8["containerService.js"] -O["extractHostData.js"] -P["writeOfflineLog.js"] +A["containerService.js"] +Q["extractHostData.js"] +R["writeOfflineLog.js"] +subgraph U["notifications"] +V["_notify.js"] +W["discord.js"] +subgraph X["data"] +Y["template.js"] end -subgraph C["middleware"] -D["authMiddleware.js"] -E["rateLimiter.js"] +Z["email.js"] +10["pushbullet.js"] +11["pushover.js"] +12["slack.js"] +13["telegram.js"] +14["whatsapp.js"] end -subgraph F["routes"] -subgraph G["auth"] -H["routes.js"] end -subgraph I["data"] +9["child_process"] +subgraph E["middleware"] +F["authMiddleware.js"] +G["rateLimiter.js"] +end +subgraph H["routes"] +subgraph I["auth"] J["routes.js"] end -subgraph K["frontendController"] +subgraph K["data"] L["routes.js"] end -subgraph M["getter"] +subgraph M["frontendController"] N["routes.js"] end -subgraph Q["setter"] -R["routes.js"] +subgraph O["getter"] +P["routes.js"] +end +subgraph S["notifications"] +T["routes.js"] +end +subgraph 15["setter"] +16["routes.js"] end end -S["server.js"] -subgraph T["swagger"] -U["swaggerDocs.js"] +17["server.js"] +subgraph 18["swagger"] +19["swaggerDocs.js"] end 4-->6 7-->1 -7-->8 +8-->1 +8-->A 8-->9 -8-->6 -B-->1 -B-->7 -J-->1 -L-->A -N-->9 -N-->B -N-->8 -N-->6 -N-->O -N-->P -R-->B -S-->B -S-->D -S-->E -S-->H -S-->J -S-->L -S-->N -S-->R -S-->U -U-->2 +A-->B +A-->6 +D-->1 +D-->8 +L-->1 +N-->C +P-->B +P-->D +P-->A +P-->6 +P-->Q +P-->R +T-->V +V-->W +V-->Z +V-->10 +V-->11 +V-->12 +V-->13 +V-->14 +W-->Y +Z-->Y +10-->Y +11-->Y +12-->Y +13-->Y +14-->Y +16-->D +17-->D +17-->F +17-->G +17-->J +17-->L +17-->N +17-->P +17-->T +17-->16 +17-->19 +19-->2 diff --git a/misc/dependencyGraphs/mermaid-api.txt b/misc/dependencyGraphs/mermaid-api.txt index c2dd6c86..0ae832b1 100644 --- a/misc/dependencyGraphs/mermaid-api.txt +++ b/misc/dependencyGraphs/mermaid-api.txt @@ -13,21 +13,23 @@ subgraph 5["controllers"] 6["scheduler.js"] 8["fetchData.js"] end -subgraph 9["utils"] -A["containerService.js"] -B["dockerClient.js"] -C["extractHostData.js"] -D["writeOfflineLog.js"] +9["child_process"] +subgraph A["utils"] +B["containerService.js"] +C["dockerClient.js"] +D["extractHostData.js"] +E["writeOfflineLog.js"] end 2-->4 2-->6 -2-->A 2-->B 2-->C 2-->D +2-->E 6-->7 6-->8 8-->7 -8-->A -A-->4 -A-->B +8-->B +8-->9 +B-->4 +B-->C diff --git a/misc/dependencyGraphs/mermaid-conf.txt b/misc/dependencyGraphs/mermaid-conf.txt index 65e4b74a..6e06cc6a 100644 --- a/misc/dependencyGraphs/mermaid-conf.txt +++ b/misc/dependencyGraphs/mermaid-conf.txt @@ -11,16 +11,18 @@ subgraph 3["controllers"] end subgraph 5["config"] 6["db.js"] -A["dockerConfig.json"] +B["dockerConfig.json"] end -subgraph 8["utils"] -9["containerService.js"] -B["dockerClient.js"] +8["child_process"] +subgraph 9["utils"] +A["containerService.js"] +C["dockerClient.js"] end 2-->4 4-->6 4-->7 7-->6 -7-->9 -9-->A -9-->B +7-->A +7-->8 +A-->B +A-->C diff --git a/misc/dependencyGraphs/mermaid-notificationService.txt b/misc/dependencyGraphs/mermaid-notificationService.txt new file mode 100644 index 00000000..dbfbd46c --- /dev/null +++ b/misc/dependencyGraphs/mermaid-notificationService.txt @@ -0,0 +1,37 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["notifications"] +2["routes.js"] +end +end +subgraph 3["utils"] +subgraph 4["notifications"] +5["_notify.js"] +6["discord.js"] +subgraph 7["data"] +8["template.js"] +end +9["email.js"] +A["pushbullet.js"] +B["pushover.js"] +C["slack.js"] +D["telegram.js"] +E["whatsapp.js"] +end +end +2-->5 +5-->6 +5-->9 +5-->A +5-->B +5-->C +5-->D +5-->E +6-->8 +9-->8 +A-->8 +B-->8 +C-->8 +D-->8 +E-->8 From 2d3af90ece7948ce7442675b4eecb5a3f4fa259c Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 6 Nov 2024 01:37:52 +0100 Subject: [PATCH 015/135] Default route rewrite --- data/database.db | Bin 610304 -> 610304 bytes server.js | 5 +++++ 2 files changed, 5 insertions(+) diff --git a/data/database.db b/data/database.db index d916b79f6501db2292480baf31d83d3c2cb38163..d9d2a74b9e61093b16a7f252a33a5461125c2bf9 100644 GIT binary patch delta 1475 zcmY*ZO=w(I6wZ4y^WK|~G%uN#K@-ztCJBr&dEEb>sL-8I#8wog?V=qqxUkhxQz|Mt zX+^h*uX>;bi6H7mFw{0s3T75A{3&GIy3p*Dx=5u;k@|DyrO!+j=gyh?op;YY-}%mM zu1+^srcEk~;6c|iSaIAMv@Qq3uzb_s7-WV}YvRt5nt=N6 z{zI;&&>r$?j!w^qi6{Klv?M|?78^}d$IezyEu1=j`jz@~3)Oi_rDej}q=TyW-Mf}R>@c#@UV`+Eulu!Zf&w~g(!;|`N5)PQ)+z{o`3L0~U-RImIDPZfiZ*rV- zSPGo6yhblsd%2VxKJwLomNJ%vGKOuznqgujn9o~1TTND8i=c7Ti=gsFAxbX%^?fM0 zR$9b@8il=;6i)w`m;13t*lNLS7wuRPL*)^|Q!aum23p75sJ9Efc`W#@AO*1&+IRd~ znNn?bi{*k*WSqg)j8iIVN|_QE0hLCf6l7rX(NPJn|LuS63JLT>Zw6X%E`sJh_u3%# z8+90W6qyQA!^7i&ElX)7mY8v>wct|GG>eB>5QddGC-UzCnBf7q@oyn4a52y6m7jfY zV%=-zp7H(urE;NX>kQN%$(LdCQ}3-wrnyl@#mGwPpgy_;M2X^f|3Cbx5sp$K7~FLc zs+BNE#oA}yu(yLk{e~aqJNvNQ2(Uljcz}&S<-`Q1lH&oK0v14*whs=3HqLI(omWWYWGTN*be^@Yuq9oU3W{c`e^am6q03- zwa%I@a!(CHa4HngIdAe_rciB4U_eHu<=7=lQjK~$aM}<*1Lz@d7B>6y&5Do&r`5_B z)8LQUOO@gV=twXNdgMv^ZuTDZ-inty68f(=|UH6z#t+SQce9q zG-(BYK(K8nN3u#G=%!jrpl?yd&QfgAY`V2wiZ0rXh`8`flSwAvh4UWV@4S1y`8+6@9q9q3kJg9lQ_*|dqWW;!Gcpb_cO~9p>}gHy-?1E2GBDn7~pcf zvYLAEM)=%XDZqQLP4UV+4^gTOc z;rc0fC5akC`)EKCjo9r%Xm}( zkN?0~nXsPJ#rq%df1}JSRVRhVYfU!Bb~o|d8?23g{K(U)oYm^a zJz zSbugR;PRjBuTc^Ql+KN#J}WT72K?d<4^2g_7%-6oqse34Z}{WY9@Mi>gYWKoe0Yal z9hEW&@+mnaI2k~J#BV#?kCB^ba1J!pHfR}e?U|&>Jxu|@*!?}ul3l>t^_{zIJpBv% cyh)=?bK-Jgi5%UzP5+*?kL$@-_1g3Q0XXzQj{pDw diff --git a/server.js b/server.js index 35350977..1c03ae99 100644 --- a/server.js +++ b/server.js @@ -37,6 +37,11 @@ app.use("/data", limiter, authMiddleware, data); app.use("/frontend", limiter, authMiddleware, frontend); app.use("/notification-service", limiter, authMiddleware, notificationService); +// Default route +router.get("/", (req, res) => { + res.redirect("/api-docs"); +}); + app.listen(PORT, () => { logger.info(`Server is running on http://localhost:${PORT}`); logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs`); From 74e7af2ed33d9f19b0d80501dbdc77612ad835d9 Mon Sep 17 00:00:00 2001 From: Its4Nik Date: Wed, 6 Nov 2024 01:44:53 +0100 Subject: [PATCH 016/135] Hot-Fix missing import --- data/database.db | Bin 610304 -> 610304 bytes server.js | 1 + 2 files changed, 1 insertion(+) diff --git a/data/database.db b/data/database.db index d9d2a74b9e61093b16a7f252a33a5461125c2bf9..fdb4b87da60d1775b970169b9ed2cc8b0bcd51d4 100644 GIT binary patch delta 1315 zcmZ8hO=w(I6wZC~=G~W&ArogZp(Ztx`6)%GFZcf^6f_$nxY0$skU~0(AX-|mwneI7 zrid=Y(C{>;i!KsSvvN@iorQ|Y#+@(~L9|flx(HLSASmc*W~RxwI5WKa9qu{j`@VD6 zw(4tJ_3Jaan|@?kojuaouLB7bHg!tYkAbo=wU zb9n;S*d0dbZuTuaz52;`mtX`K8H^Ak+aB*mfBo>xh1E+dm)@%^a&Ej5ib)CECQ}&W zOyIX+LPGN)Ez&-mF1=WQ*I4GOvbBo)L~7-vv%FC`zj}V<^1ID9S1XHx3&-H~5~;Ue zx=S-;UI{O?O{{m^XfEAQHkVAo-nsC2`?H^EKx$lQAthspx6D~-of%H&nt9l}Nb7T! zD`th)R>(%>=^)-n#hkPTmUvJYeVPR2Kj}0bU>hpxWQEX9r_GbqqeggcoY4$>8{DKk zlU#bIfscnp+E0PbdN36au+7tAf1d2wIZ1F*824BKUEn0jXth9DYQ(qbDA5gj2c!GDe&B<{Ed2h`FENlM8eJj zc@8e#qc6+|!JX3jV3H?}Va9T!_Gdx*WiJPo8ew7V08Ss>`*u=Gg%ZfbB4HX0W#IeS z_+*KDsSd8h9BwVj@Whz0Hk9-6~*cjMNGE-6SINf;EfQSEP^I4@9rMk0AqjEdi z%3P)Na%(y}@?aU7%V7yRJJDKA3xlHiL}PCTIsBh#nKe$P&C}IqrQ_Bp<I41 z8Jc<&HSi3j98~er<66M2BwGvxqUqWnCE7bwFmK}qbaG*z`3+!vnq6JYNGDMmdAnx}%o<^tI{hR*=*rATlM@DG1+ hfe$?(MmgrCfo3UFojiDnY@>p&*#{qY>DS%(?0*(8VFmyI delta 1163 zcmY*ZO=w(Y6rDGjdEc{4%}nQ|L8ED!WI!Z7zI*TYI~AcDMclL?F09aI5nQy1peb1V zX`-OGYnaw6c2hxgrKUtOf>4{CprBdUb+QxOOhCkipx0!kojf=^xaYk4op&s0gavS}*@PcrK4%#L0Dsh zCbHcVZT0N`^VX4|h#|^G43Xi5Q(2h62sYXw7fyo-hN5?Tc3F+3xUXQC8_AChammx_ z=Mg!I=Qop?$2W@+RO$D-dd{Ws?YJ-nbdXPdPn_}j5$vd#X-uv zC#SVxxT5kDbP$%eKsD0?g13K9=lG9*(|VP3+uF^$DxIp_DXBXfjj3;yTKCJ<(#{J! z|6Jub|N3QmwFM5O4Hc3eS&oYs4y{H)NQB(jF3&tl&Re7qC3yoo;I+&%Fw`nd)#v%- zdR4sbGkh&qZJv5Q?KEV1lIOx;i0EC=p}YH$x-oK$0(xIqfGC?oG{N%HqH5<6hrcc_ zi2~x?@$y)eJVfNJbMlcLlzr?Z&wN!!>1}-h`!_JAvp(q_amG<3I}~2BvS Date: Fri, 20 Dec 2024 22:03:41 +0100 Subject: [PATCH 017/135] Switch to ES6 and TypeScript (#21) * Full ES6 support * Full ES6 support * TODO: fix npm run dev in ts * TODO: fix 'ERROR : Error fetching data: ' * Delete files * Adding more typing; making code more logical at some points; * Added typing and fixed > dockstatapi@2 dep > bash ./src/utils/createDependencyGraph.sh Route: frontend Route: auth ./routes/frontendController/routes.ts ./routes/auth/routes.ts Route: data ./routes/data/routes.ts Route: notificationService ./routes/notifications/routes.ts Route: api ./routes/getter/routes.ts Route: conf ./routes/setter/routes.ts ======== DONE ======== * Added typing and fixed 'npm run dep' * Advanced logging and fixing some bugs * Adjust workflows * New README and some other docer adjustments * First time building (god damn) * Fixing some typings * Fixing some typings in default notification modules * Needs fixing! * Needs fixing! * Create CodeQL.yml * Fixing more errors * Added some more typings and better /api/status route * New mermaid deiagrams * New mermaid deiagrams * HA route * No building errors! * Remove CodeQL * Use selfhosted runners * nerver mind, using too much ressources on my cloud machine * Added HA functionality (Please test) * Fixing package versions * Fix: Creating default config if it doesn't exist * Fix: Sync endpoint * Fix: Endpoint reachability check * Chore: Update notification service * CI: Added cloc * Fix: adjust cloc workflox * Fix: exclude node modules from cloc * Fix: we have no yaml files, probably due to some other actions or smth * Fix: exclude package-lock from cloc * Created new dependency graphs * Feat: Added playwright tests for API endpoint + Swagger test (auth) (#23) Co-authored-by: ItsNik * Chore: Cleanup * Fix: Added proxy support * Chore: Custom notifications * Feat: minified build Chore: Alpine based Dockerfile Chore: Advance Lifecycle scripts included in dockstatapi@2: start tsx src/server.ts available via `npm run-script`: start:build npx tsc && export NODE_NO_WARNINGS=1 && node dist/server.js dev nodemon dev:trace nodemon --trace-uncaught --trace-warnings dep bash ./src/utils/createDependencyGraph.sh dep:remove bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh build npx tsc build:mini npx tsc && bash ./src/misc/minifyDist.sh --build-only mini bash ./src/misc/minifyDist.sh scripts * Feat: minified build Chore: Alpine based Dockerfile Chore: Advance Lifecycle scripts included in dockstatapi@2: start tsx src/server.ts available via `npm run-script`: start:build npx tsc && export NODE_NO_WARNINGS=1 && node dist/server.js dev nodemon dev:trace nodemon --trace-uncaught --trace-warnings dep bash ./src/utils/createDependencyGraph.sh dep:remove bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh build npx tsc build:mini npx tsc && bash ./src/misc/minifyDist.sh --build-only mini bash ./src/misc/minifyDist.sh scripts * Better docker image * Chore: Update docker image to alpine base * Fix: use find instead of tree in minifyDist.sh * Chore: Update Readme * Fix: Force correct node version (took some time to find it) * Fix: Typo in package.json * Feat: added npmc * Fix: Remove data-bak * Feat: Switch to yarn in Dockerfile * Feat: Switch to yarn in Dockerfile * Fix: Yarn does not work => Change to npm * Fix: Specify Node version using nvmrc in workflow file * Test: Try npm i --verbose to see why workflow times out * Test: Try with all environment files * Warn: Removing arm/v7 support due to docker build incompatibilities * Chore: Add test build for debugging with dockstatapi: * Chore: Add opencontainer labels * Chore: Added automatic notifications Chore: Add init.ts instead of one big server.ts Fix: Allow usePassword.txt in ./src/data (previously not included) * Fix: Adjusted .gitignore * Chore: Changed from ? true : false to simpler syntax * Chore: Added lock file when a sync is running * Chore: Remove any typing highAvailability.ts Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Chore: Update src/utils/connectionChecker.ts Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Missing Typing for HA * Fix: Environment Varaible usage inside docker * Fix: Forgot the copying of the file inside the Dockerfile (bruh) --------- Co-authored-by: ItsNik Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- .dockerignore | 2 + .github/DockStat.png | Bin 0 -> 79885 bytes .github/workflows/anchore.yml | 36 +- .github/workflows/build-dev.yaml | 2 +- .github/workflows/build-image.yml | 2 +- .github/workflows/cloc.yaml | 28 + .github/workflows/test-build.yaml | 59 + .gitignore | 15 +- .npmrc | 1 + .nvmrc | 1 + Dockerfile | 93 +- README.md | 36 +- TODO.md | 12 + config/db.js | 19 - config/dockerConfig.json | 9 - config/loggerConfig.js | 18 - config/swaggerConfig.js | 29 - controllers/fetchData.js | 59 - data/database.db | Bin 610304 -> 0 bytes entrypoint.sh | 26 - environment.d.ts | 44 + middleware/authMiddleware.js | 50 - middleware/password.json | 1 - misc/dependencyGraphs/mermaid-all.txt | 106 - misc/dependencyGraphs/mermaid-api.txt | 35 - misc/dependencyGraphs/mermaid-conf.txt | 28 - .../mermaid-notificationService.txt | 37 - misc/entrypoint.sh | 26 - nodemon.json | 6 + package-lock.json | 3449 +++++++++++++---- package.json | 55 +- playwright.config.ts | 37 + routes/auth/routes.js | 146 - routes/data/routes.js | 111 - routes/setter/routes.js | 145 - server.js | 49 - .../.dependency-cruiser.cjs | 255 +- src/config/db.ts | 30 + src/config/hostsystem.ts | 61 + src/config/loggerConfig.ts | 45 + src/config/swaggerConfig.ts | 53 + .../controllers/containerController.ts | 27 +- .../controllers/databaseMigration.ts | 12 +- src/controllers/fetchData.ts | 80 + .../controllers/frontendConfiguration.ts | 91 +- src/controllers/highAvailability.ts | 274 ++ src/controllers/notificationController.ts | 62 + src/controllers/proxy.ts | 14 + .../controllers/scheduler.ts | 53 +- {middleware => src/data}/usePassword.txt | 0 src/init.ts | 47 + src/middleware/authMiddleware.ts | 52 + src/middleware/checkLock.ts | 19 + .../middleware/rateLimiter.ts | 0 src/misc/createEnvFile.sh | 34 + src/misc/dependencyGraphs/mermaid-all.txt | 106 + src/misc/dependencyGraphs/mermaid-api.txt | 32 + .../misc}/dependencyGraphs/mermaid-auth.txt | 2 +- src/misc/dependencyGraphs/mermaid-conf.txt | 24 + .../misc}/dependencyGraphs/mermaid-data.txt | 4 +- .../dependencyGraphs/mermaid-frontend.txt | 4 +- src/misc/dependencyGraphs/mermaid-ha.txt | 11 + .../mermaid-notificationService.txt | 35 + src/misc/entrypoint.sh | 30 + src/misc/minifyDist.sh | 38 + src/routes/auth/routes.ts | 174 + src/routes/data/routes.ts | 201 + .../routes/frontendController/routes.ts | 33 +- .../routes.js => src/routes/getter/routes.ts | 160 +- src/routes/highavailability/routes.ts | 92 + .../routes/notifications/routes.ts | 76 +- src/routes/setter/routes.ts | 180 + src/server.ts | 17 + src/utils/connectionChecker.ts | 77 + src/utils/containerService.ts | 134 + src/utils/createDependencyGraph.sh | 37 + src/utils/dockerClient.ts | 54 + src/utils/extractHostData.ts | 57 + utils/logger.js => src/utils/logger.ts | 8 +- src/utils/notifications/_notify.ts | 85 + .../utils/notifications/_template.ts | 42 +- src/utils/notifications/discord.ts | 55 + src/utils/notifications/email.ts | 46 + src/utils/notifications/pushbullet.ts | 59 + src/utils/notifications/pushover.ts | 56 + src/utils/notifications/slack.ts | 55 + src/utils/notifications/telegram.ts | 55 + src/utils/notifications/whatsapp.ts | 57 + src/utils/removeUnusedDeps.sh | 36 + src/utils/swaggerDocs.ts | 11 + src/utils/writeOfflineLog.ts | 26 + swagger/swaggerDocs.js | 10 - tests/main.spec.ts | 131 + tsconfig.json | 21 + utils/containerService.js | 63 - utils/createDependencyGraph.sh | 34 - utils/dockerClient.js | 45 - utils/extractHostData.js | 26 - utils/notifications/_notify.js | 59 - utils/notifications/data/template.json | 3 - utils/notifications/discord.js | 27 - utils/notifications/email.js | 36 - utils/notifications/pushbullet.js | 30 - utils/notifications/pushover.js | 30 - utils/notifications/slack.js | 27 - utils/notifications/telegram.js | 32 - utils/notifications/whatsapp.js | 29 - utils/writeOfflineLog.js | 31 - yarn.lock | 3298 ++++++++++++++++ 109 files changed, 9584 insertions(+), 2498 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/DockStat.png create mode 100644 .github/workflows/cloc.yaml create mode 100644 .github/workflows/test-build.yaml create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 TODO.md delete mode 100644 config/db.js delete mode 100644 config/dockerConfig.json delete mode 100644 config/loggerConfig.js delete mode 100644 config/swaggerConfig.js delete mode 100644 controllers/fetchData.js delete mode 100644 data/database.db delete mode 100644 entrypoint.sh create mode 100644 environment.d.ts delete mode 100644 middleware/authMiddleware.js delete mode 100644 middleware/password.json delete mode 100644 misc/dependencyGraphs/mermaid-all.txt delete mode 100644 misc/dependencyGraphs/mermaid-api.txt delete mode 100644 misc/dependencyGraphs/mermaid-conf.txt delete mode 100644 misc/dependencyGraphs/mermaid-notificationService.txt delete mode 100755 misc/entrypoint.sh create mode 100644 nodemon.json create mode 100644 playwright.config.ts delete mode 100644 routes/auth/routes.js delete mode 100644 routes/data/routes.js delete mode 100644 routes/setter/routes.js delete mode 100644 server.js rename .dependency-cruiser.js => src/.dependency-cruiser.cjs (66%) create mode 100644 src/config/db.ts create mode 100644 src/config/hostsystem.ts create mode 100644 src/config/loggerConfig.ts create mode 100644 src/config/swaggerConfig.ts rename controllers/containerController.js => src/controllers/containerController.ts (58%) rename controllers/databaseMigration.js => src/controllers/databaseMigration.ts (55%) create mode 100644 src/controllers/fetchData.ts rename controllers/frontendConfiguration.js => src/controllers/frontendConfiguration.ts (73%) create mode 100644 src/controllers/highAvailability.ts create mode 100644 src/controllers/notificationController.ts create mode 100644 src/controllers/proxy.ts rename controllers/scheduler.js => src/controllers/scheduler.ts (56%) rename {middleware => src/data}/usePassword.txt (100%) create mode 100644 src/init.ts create mode 100644 src/middleware/authMiddleware.ts create mode 100644 src/middleware/checkLock.ts rename middleware/rateLimiter.js => src/middleware/rateLimiter.ts (100%) create mode 100644 src/misc/createEnvFile.sh create mode 100644 src/misc/dependencyGraphs/mermaid-all.txt create mode 100644 src/misc/dependencyGraphs/mermaid-api.txt rename {misc => src/misc}/dependencyGraphs/mermaid-auth.txt (80%) create mode 100644 src/misc/dependencyGraphs/mermaid-conf.txt rename {misc => src/misc}/dependencyGraphs/mermaid-data.txt (78%) rename {misc => src/misc}/dependencyGraphs/mermaid-frontend.txt (71%) create mode 100644 src/misc/dependencyGraphs/mermaid-ha.txt create mode 100644 src/misc/dependencyGraphs/mermaid-notificationService.txt create mode 100755 src/misc/entrypoint.sh create mode 100644 src/misc/minifyDist.sh create mode 100644 src/routes/auth/routes.ts create mode 100644 src/routes/data/routes.ts rename routes/frontendController/routes.js => src/routes/frontendController/routes.ts (97%) rename routes/getter/routes.js => src/routes/getter/routes.ts (68%) create mode 100644 src/routes/highavailability/routes.ts rename routes/notifications/routes.js => src/routes/notifications/routes.ts (73%) create mode 100644 src/routes/setter/routes.ts create mode 100644 src/server.ts create mode 100644 src/utils/connectionChecker.ts create mode 100644 src/utils/containerService.ts create mode 100755 src/utils/createDependencyGraph.sh create mode 100644 src/utils/dockerClient.ts create mode 100644 src/utils/extractHostData.ts rename utils/logger.js => src/utils/logger.ts (52%) create mode 100644 src/utils/notifications/_notify.ts rename utils/notifications/data/template.js => src/utils/notifications/_template.ts (51%) create mode 100644 src/utils/notifications/discord.ts create mode 100644 src/utils/notifications/email.ts create mode 100644 src/utils/notifications/pushbullet.ts create mode 100644 src/utils/notifications/pushover.ts create mode 100644 src/utils/notifications/slack.ts create mode 100644 src/utils/notifications/telegram.ts create mode 100644 src/utils/notifications/whatsapp.ts create mode 100755 src/utils/removeUnusedDeps.sh create mode 100644 src/utils/swaggerDocs.ts create mode 100644 src/utils/writeOfflineLog.ts delete mode 100644 swagger/swaggerDocs.js create mode 100644 tests/main.spec.ts create mode 100644 tsconfig.json delete mode 100644 utils/containerService.js delete mode 100755 utils/createDependencyGraph.sh delete mode 100644 utils/dockerClient.js delete mode 100644 utils/extractHostData.js delete mode 100644 utils/notifications/_notify.js delete mode 100644 utils/notifications/data/template.json delete mode 100644 utils/notifications/discord.js delete mode 100644 utils/notifications/email.js delete mode 100644 utils/notifications/pushbullet.js delete mode 100644 utils/notifications/pushover.js delete mode 100644 utils/notifications/slack.js delete mode 100644 utils/notifications/telegram.js delete mode 100644 utils/notifications/whatsapp.js delete mode 100644 utils/writeOfflineLog.js create mode 100644 yarn.lock diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..10b44aec --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +*.txt +*.md \ No newline at end of file diff --git a/.github/DockStat.png b/.github/DockStat.png new file mode 100644 index 0000000000000000000000000000000000000000..d375bd49107c79a960488d6062276a72cf6bd512 GIT binary patch literal 79885 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⪼EggaS+_T%ej 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 KU+70|um2a&GMD85 literal 0 HcmV?d00001 diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 053123d0..bb5df127 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -13,12 +13,12 @@ name: Anchore Grype vulnerability scan on: push: - branches: [ "main" ] + branches: ["main"] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ["main", "dev"] schedule: - - cron: '30 9 * * 1' + - cron: "30 9 * * 1" permissions: contents: read @@ -31,18 +31,18 @@ jobs: actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status runs-on: ubuntu-latest steps: - - name: Check out the code - uses: actions/checkout@v4 - - name: Build the Docker image - run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - name: Run the Anchore Grype scan action - uses: anchore/scan-action@d5aa5b6cb9414b0c7771438046ff5bcfa2854ed7 - id: scan - with: - image: "localbuild/testimage:latest" - fail-build: true - severity-cutoff: critical - - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ${{ steps.scan.outputs.sarif }} + - name: Check out the code + uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag localbuild/testimage:latest + - name: Run the Anchore Grype scan action + uses: anchore/scan-action@d5aa5b6cb9414b0c7771438046ff5bcfa2854ed7 + id: scan + with: + image: "localbuild/testimage:latest" + fail-build: true + severity-cutoff: critical + - name: Upload vulnerability report + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ${{ steps.scan.outputs.sarif }} diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index 9833ede7..afc8ffc1 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -1,4 +1,4 @@ -name: Docker Image CI (dev) +name: Build dockstatapi:nightly on: push: diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 8668f9ba..4097b0b0 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,4 +1,4 @@ -name: Docker Image CI +name: Buiod dockstatapi:latest on: release: diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml new file mode 100644 index 00000000..9ce7e271 --- /dev/null +++ b/.github/workflows/cloc.yaml @@ -0,0 +1,28 @@ +name: Count Lines of Code + +permissions: + issues: write + pull-requests: write + +on: + push: + branches: [ main ] + pull_request: + branches: [ main, dev ] + +jobs: + cloc: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Count Lines of Code (cloc) + uses: djdefi/cloc-action@6 + with: + options: --md --report-file=cloc.md --exclude-dir=node_modules --exclude-lang=YAML,JSON --exclude-list-file=package-lock.json + + - name: Create comment from markdown file + uses: GrantBirki/comment@v2.1.0 + with: + file: cloc.md \ No newline at end of file diff --git a/.github/workflows/test-build.yaml b/.github/workflows/test-build.yaml new file mode 100644 index 00000000..fb471834 --- /dev/null +++ b/.github/workflows/test-build.yaml @@ -0,0 +1,59 @@ +name: Test building + +on: + pull_request: + branches: + - "dev" + +permissions: + packages: write + contents: read + +jobs: + build-main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Set up Node.js using nvm + - name: Set up Node.js version from .nvmrc + run: | + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + nvm install + nvm use + node -v + npm -v + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} + + - name: Build and Push Docker Images + uses: docker/build-push-action@v5 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index f7fcc52b..43ddf882 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,13 @@ # custom paths: -data/* - +src/data/database.db +src/data/dockerConfig.json +src/data/highAvailability.json +src/data/states.json +src/data/user.conf +src/data/password.json +src/data/ha.lock + +.test* # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### # Logs @@ -141,3 +148,7 @@ dist # SvelteKit build / generate output .svelte-kit +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..4fd02195 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..209e3ef4 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/Dockerfile b/Dockerfile index b23d93c2..87792b05 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,34 +1,59 @@ -# Stage 1: Build stage -FROM node:latest AS builder - -LABEL maintainer="https://github.com/its4nik" -LABEL version="2" -LABEL description="API for DockStat" -LABEL license="BSD-3-Clause license " -LABEL repository="https://github.com/its4nik/dockstatapi" -LABEL documentation="https://github.com/its4nik/dockstatapi" - -WORKDIR /api - -COPY package*.json ./ - -RUN npm install --production - -COPY . . - -# Stage 2: Production stage -FROM node:alpine - -WORKDIR /api - -COPY --from=builder /api . - -RUN apk add --no-cache \ - bash \ - curl - -EXPOSE 7070 - -HEALTHCHECK CMD curl --fail http://localhost:7070/api/status || exit 1 - -ENTRYPOINT [ "bash", "misc/entrypoint.sh" ] +# Stage 1: Build stage +FROM node:alpine AS builder + +LABEL maintainer="https://github.com/its4nik" +LABEL version="2" +LABEL description="API for DockStat" +LABEL license="BSD-3-Clause license" +LABEL repository="https://github.com/its4nik/dockstatapi" +LABEL documentation="https://github.com/its4nik/dockstatapi" +LABEL org.opencontainers.image.description "The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" +LABEL org.opencontainers.image.licenses="BSD-3-Clause license" +LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" + +WORKDIR /build +ENV NODE_NO_WARNINGS=1 + +RUN apk update && \ + apk upgrade && \ + apk add bash + + +COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +RUN npm install --verbose + +COPY ./src ./src +RUN npm run build:mini + +# Stage 2: main stage +FROM alpine AS main + +# Needed packages +RUN apk update && \ + apk upgrade && \ + apk add --update npm + +WORKDIR /build + +RUN mkdir -p /build/src/data + +COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +RUN npm install --omit=dev --verbose + +COPY --from=builder /build/dist/* /build/src +COPY --from=builder /build/src/misc/entrypoint.sh /build/entrypoint.sh +COPY --from=builder /build/src/misc/createEnvFile.sh /build/createEnvFile.sh + +RUN node src/config/db.js + +# Stage 3: Production stage +FROM alpine AS production +ARG RUNNING_IN_DOCKER=true +RUN apk add --update bash nodejs + +WORKDIR /api + +COPY --from=main /build /api + +EXPOSE 9876 +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/README.md b/README.md index c12afae4..ae34767f 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,45 @@ # DockStatAPI v2 +![Dockstat Logo](.github/DockStat.png) This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. -With this new release a cupple of extra features (compared to v1) are going to be available. +With this new release a couple of extra features (compared to v1) are going to be available. ### Feature List: - Swagger API Documentation -- "Offline" mode (useful when working on the backend without available test docker sockets) - Database (Keeps data for 24 hours max) - Advanced authentication using hashes and salt +- Custom TypeScript/JavaScript notification modules! (Easy to add and configure!) +- `http` API to configure the backend +- Multi-arch docker builds (using buildx github action) +- Advanced security through middlewares: rate-limiting and authentication +- Multi Arch Docker builds through docker buildx +- High Availability using single master and ulimited worker nodes! # 🔗 DockStatAPI v2 Documentation + +_⚠️ = Deprecation warning_ + +- [Introduction](https://outline.itsnik.de/s/dockstat) + + - [DockstatAPI v2](https://outline.itsnik.de/s/dockstat/doc/dockstatapi-v2-XRMDKRqMIg) + + - [API reference](https://outline.itsnik.de/s/dockstat/doc/api-reference-1PTxqx1MQ6) + - [How dependency graphs are made](https://outline.itsnik.de/s/dockstat/doc/how-the-dependecy-graphs-are-made-svuZbEHH9g) + + - [DockStat v1](https://outline.itsnik.de/s/dockstat/doc/dockstat-v1-zVaFS4zROI) + + - [⚠️ Customisation](https://outline.itsnik.de/s/dockstat/doc/customization-PiBz4OpQIZ) + - [⚠️ Themes](https://outline.itsnik.de/s/dockstat/doc/themes-BFhN6ZBbYx) + - [⚠️ Installation](https://outline.itsnik.de/s/dockstat/doc/installation-DaO99bB86q) + + - [⚠️ DockStatAPI v1](https://outline.itsnik.de/s/dockstat/doc/dockstatapi-v1-jLcVCfPNmS) + - [⚠️ Integrations](https://outline.itsnik.de/s/dockstat/doc/integrations-Agq1oL6HxF) + - [⚠️ Backend API reference](https://outline.itsnik.de/s/dockstat/doc/backend-api-reference-YzcBbDvY33) + +# DockStat(APIs) goals + +DockStack tries to be a lightweigh and more "dashboard" like then [portainer](https://github.com/portainer/portainer), [cAdvisor](https://github.com/google/cadvisor), [dockge](https://github.com/louislam/dockge), ... +I also try to add some "extensions", like in V1 with [🥤cup](https://github.com/sergi0g/cup). +Everything is configured through a backend with Swagger documentation, so that you can follow the code and understand the new v2 frontend better! +DockStat is mainly used for teaching [myself](https://github.com/Its4Nik) more about TypeScript, APIs and backend development! diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..d2659e2d --- /dev/null +++ b/TODO.md @@ -0,0 +1,12 @@ +- [ ] Better Offline mode using "faker" library or self written (probably self written) +- [X] HA compatibility +- [X] !!! Needs testing !!! Add automatic notifications when container state changes, according to selected level for notification service +- [ ] Image update and update notifications +- [ ] trigger container restart / stop / start via backend routes +- [X] Add more logging +- [X] Structure code differently +- [X] Write new README and make the docs better +- [X] Update more files to correct TS syntax => remove "any" +- [ ] Websockets +- [X] Better /api/status endpoint with connection status of each host +- [X] Update notification service diff --git a/config/db.js b/config/db.js deleted file mode 100644 index 51850d3e..00000000 --- a/config/db.js +++ /dev/null @@ -1,19 +0,0 @@ -const sqlite3 = require("sqlite3").verbose(); -const logger = require("./../utils/logger"); -const path = require("path"); -const dbPath = path.join(__dirname, "../data/database.db"); - -const db = new sqlite3.Database(dbPath, (err) => { - if (err) { - logger.error("Error opening database:", err.message); - } else { - db.run(`CREATE TABLE IF NOT EXISTS data ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - info TEXT NOT NULL, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP - )`); - logger.info("Database created / opened succesfully"); - } -}); - -module.exports = db; diff --git a/config/dockerConfig.json b/config/dockerConfig.json deleted file mode 100644 index 9ec4caf3..00000000 --- a/config/dockerConfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "hosts": [ - { - "name": "Fin-2", - "url": "100.89.35.135", - "port": "2375" - } - ] -} diff --git a/config/loggerConfig.js b/config/loggerConfig.js deleted file mode 100644 index 38149ec4..00000000 --- a/config/loggerConfig.js +++ /dev/null @@ -1,18 +0,0 @@ -const { createLogger, format, transports } = require("winston"); - -const logger = createLogger({ - level: "info", - format: format.combine( - format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), - format.printf( - ({ timestamp, level, message }) => - `[${timestamp}] ${level.toUpperCase()}: ${message}`, - ), - ), - transports: [ - new transports.Console(), - new transports.File({ filename: "logs/app.log" }), - ], -}); - -module.exports = logger; diff --git a/config/swaggerConfig.js b/config/swaggerConfig.js deleted file mode 100644 index 723897fc..00000000 --- a/config/swaggerConfig.js +++ /dev/null @@ -1,29 +0,0 @@ -const options = { - definition: { - failOnErrors: true, - openapi: "3.0.0", - info: { - title: "DockStatAPI", - version: "2", - description: "An API used to query muliple docker hosts", - }, - components: { - securitySchemes: { - passwordAuth: { - type: "apiKey", - in: "header", - name: "x-password", - description: "Password required for authentication", - }, - }, - }, - security: [ - { - passwordAuth: [], - }, - ], - }, - apis: ["./routes/*/*.js"], -}; - -module.exports = options; diff --git a/controllers/fetchData.js b/controllers/fetchData.js deleted file mode 100644 index ba14c348..00000000 --- a/controllers/fetchData.js +++ /dev/null @@ -1,59 +0,0 @@ -const db = require("../config/db"); -const { fetchAllContainers } = require("../utils/containerService"); -const logger = require("./../utils/logger"); -const path = require("path"); -const fs = require("fs"); -const { exec } = require("child_process"); - -const fetchData = async () => { - try { - const allContainerData = await fetchAllContainers(); - const data = allContainerData; - - if (process.env.OFFLINE === "true") { - logger.info("No new data inserted --- OFFLINE MODE"); - } else { - db.run( - `INSERT INTO data (info) VALUES (?)`, - [JSON.stringify(data)], - function (error) { - if (error) { - logger.info("Error inserting data:", error.message); - console.error("Error inserting data:", error.message); - return; - } - logger.info(`Data inserted with ID: ${this.lastID}`); - }, - ); - } - - const containerStatus = {}; - Object.keys(allContainerData).forEach((host) => { - containerStatus[host] = allContainerData[host].map((container) => ({ - name: container.name, - id: container.id, - state: container.state, - host: container.hostName, - })); - }); - - const filePath = path.resolve(__dirname, "../data/states.json"); - let previousState = {}; - - if (fs.existsSync(filePath)) { - previousState = JSON.parse(fs.readFileSync(filePath, "utf8")); - } - - if (JSON.stringify(previousState) !== JSON.stringify(containerStatus)) { - fs.writeFileSync(filePath, JSON.stringify(containerStatus, null, 2)); - logger.info(`Container states saved to ${filePath}`); - //TODO: logic + notification levels per service - } else { - logger.info("No state change detected, notifications not triggered."); - } - } catch (error) { - logger.error("Error fetching data:", error.message); - } -}; - -module.exports = fetchData; diff --git a/data/database.db b/data/database.db deleted file mode 100644 index fdb4b87da60d1775b970169b9ed2cc8b0bcd51d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 610304 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 T!%x$uzO8Xp=N2W3`G5X@A5s6M diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 2008cdbd..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -cat << EOF -Welcome to - - ###### ###### #### ### ### #### ######### ###### ######### - ### ### ### ### ### ### ### ### ### ### ### ### - ### ### ### ### ### ###### #### ### ### ### ### - ### ### ### ### ### ### ### #### ### ############ ### - ### ### ### ### ### ### ### #### ### ### ### ### - ###### ###### #### ### ### #### ### ### ### ### (API) - -Useful links: - -- Documentation: https://outline.itsnik.de/s/dockstat -- GitHub (Frontend): https://github.com/its4nik/dockstat -- GitHub (Backend): https://github.com/its4nik/dockstatapi -- API Documentation: http://localhost:7000/api-docs - -Summary: - -DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. - -EOF - -npm run start diff --git a/environment.d.ts b/environment.d.ts new file mode 100644 index 00000000..803ae43c --- /dev/null +++ b/environment.d.ts @@ -0,0 +1,44 @@ +declare global { + namespace NodeJS { + interface ProcessEnv { + // Node specific: + NODE_ENV: "development" | "production"; + TRUSTED_PROXYS: string | undefined; + + // User.conf + RUNNING_IN_DOCKER: string | undefined; + VERSION: string | undefined; + + // High Availability + HA_MASTER: string | undefined; //bool + HA_MASTER_IP: string | undefined; + HA_NODE: string | undefined; //ip list with port seperated by "," like: "10.0.0.4:5012,10.0.0.5:9876" + HA_UNSAFE: string | undefined; + + // Notification services: + DISCORD_WEBHOOK_URL: string | undefined; + + EMAIL_SENDER: string | undefined; + EMAIL_RECIPIENT: string | undefined; + EMAIL_PASSWORD: string | undefined; + EMAIL_SERVICE: string | undefined; + + PUSHBULLET_ACCESS_TOKEN: string | undefined; + + PUSHOVER_USER_KEY: string | undefined; + PUSHOVER_API_TOKEN: string | undefined; + + SLACK_WEBHOOK_URL: string | undefined; + + TELEGRAM_BOT_TOKEN: string | undefined; + TELEGRAM_CHAT_ID: string | undefined; + + WHATSAPP_API_URL: string | undefined; + WHATSAPP_RECIPIENT: string | undefined; + + CUSTOM_NOTIFICATION: string | undefined; // enter the script name without .js here and without custom/... + } + } +} + +export {}; diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js deleted file mode 100644 index 8ee6a688..00000000 --- a/middleware/authMiddleware.js +++ /dev/null @@ -1,50 +0,0 @@ -const bcrypt = require("bcrypt"); -const fs = require("fs"); -const path = require("path"); -const logger = require("../utils/logger"); -const passwordFile = path.join(__dirname, "password.json"); -const passwordBool = path.join(__dirname, "usePassword.txt"); - -function authMiddleware(req, res, next) { - fs.readFile(passwordBool, "utf8", (err, data) => { - if (err) { - logger.error("Error reading the file:", err); - return; - } - - const isAuthEnabled = data.trim() === "true"; - - if (!isAuthEnabled) { - return next(); - } - - const providedPassword = req.headers["x-password"]; - if (!providedPassword) { - logger.error("Password required - Denied"); - return res.status(401).json({ message: "Password required" }); - } - - fs.readFile(passwordFile, "utf8", (err, data) => { - if (err) { - logger.error("Error reading password"); - return res.status(500).json({ message: "Error reading password" }); - } - - const storedData = JSON.parse(data); - bcrypt.compare(providedPassword, storedData.hash, (err, result) => { - if (err) { - logger.error("Error validating password - Denied access"); - return res.status(500).json({ message: "Error validating password" }); - } - if (!result) { - console.error("Invalid Password - Denied access"); - return res.status(401).json({ message: "Invalid password" }); - } - - next(); - }); - }); - }); -} - -module.exports = authMiddleware; diff --git a/middleware/password.json b/middleware/password.json deleted file mode 100644 index 37a7c4c4..00000000 --- a/middleware/password.json +++ /dev/null @@ -1 +0,0 @@ -{"hash":"$2b$10$qGcNmciEGhX.PiB.ofHib.Fob.nOjQNfguBoD4JDbbbTysrLrKGEi","salt":"$2b$10$qGcNmciEGhX.PiB.ofHib."} \ No newline at end of file diff --git a/misc/dependencyGraphs/mermaid-all.txt b/misc/dependencyGraphs/mermaid-all.txt deleted file mode 100644 index 6036bdd7..00000000 --- a/misc/dependencyGraphs/mermaid-all.txt +++ /dev/null @@ -1,106 +0,0 @@ -flowchart LR - -subgraph 0["config"] -1["db.js"] -2["swaggerConfig.js"] -B["dockerConfig.json"] -end -subgraph 3["controllers"] -4["containerController.js"] -7["databaseMigration.js"] -8["fetchData.js"] -C["frontendConfiguration.js"] -D["scheduler.js"] -end -subgraph 5["utils"] -6["dockerClient.js"] -A["containerService.js"] -Q["extractHostData.js"] -R["writeOfflineLog.js"] -subgraph U["notifications"] -V["_notify.js"] -W["discord.js"] -subgraph X["data"] -Y["template.js"] -end -Z["email.js"] -10["pushbullet.js"] -11["pushover.js"] -12["slack.js"] -13["telegram.js"] -14["whatsapp.js"] -end -end -9["child_process"] -subgraph E["middleware"] -F["authMiddleware.js"] -G["rateLimiter.js"] -end -subgraph H["routes"] -subgraph I["auth"] -J["routes.js"] -end -subgraph K["data"] -L["routes.js"] -end -subgraph M["frontendController"] -N["routes.js"] -end -subgraph O["getter"] -P["routes.js"] -end -subgraph S["notifications"] -T["routes.js"] -end -subgraph 15["setter"] -16["routes.js"] -end -end -17["server.js"] -subgraph 18["swagger"] -19["swaggerDocs.js"] -end -4-->6 -7-->1 -8-->1 -8-->A -8-->9 -A-->B -A-->6 -D-->1 -D-->8 -L-->1 -N-->C -P-->B -P-->D -P-->A -P-->6 -P-->Q -P-->R -T-->V -V-->W -V-->Z -V-->10 -V-->11 -V-->12 -V-->13 -V-->14 -W-->Y -Z-->Y -10-->Y -11-->Y -12-->Y -13-->Y -14-->Y -16-->D -17-->D -17-->F -17-->G -17-->J -17-->L -17-->N -17-->P -17-->T -17-->16 -17-->19 -19-->2 diff --git a/misc/dependencyGraphs/mermaid-api.txt b/misc/dependencyGraphs/mermaid-api.txt deleted file mode 100644 index 0ae832b1..00000000 --- a/misc/dependencyGraphs/mermaid-api.txt +++ /dev/null @@ -1,35 +0,0 @@ -flowchart LR - -subgraph 0["routes"] -subgraph 1["getter"] -2["routes.js"] -end -end -subgraph 3["config"] -4["dockerConfig.json"] -7["db.js"] -end -subgraph 5["controllers"] -6["scheduler.js"] -8["fetchData.js"] -end -9["child_process"] -subgraph A["utils"] -B["containerService.js"] -C["dockerClient.js"] -D["extractHostData.js"] -E["writeOfflineLog.js"] -end -2-->4 -2-->6 -2-->B -2-->C -2-->D -2-->E -6-->7 -6-->8 -8-->7 -8-->B -8-->9 -B-->4 -B-->C diff --git a/misc/dependencyGraphs/mermaid-conf.txt b/misc/dependencyGraphs/mermaid-conf.txt deleted file mode 100644 index 6e06cc6a..00000000 --- a/misc/dependencyGraphs/mermaid-conf.txt +++ /dev/null @@ -1,28 +0,0 @@ -flowchart LR - -subgraph 0["routes"] -subgraph 1["setter"] -2["routes.js"] -end -end -subgraph 3["controllers"] -4["scheduler.js"] -7["fetchData.js"] -end -subgraph 5["config"] -6["db.js"] -B["dockerConfig.json"] -end -8["child_process"] -subgraph 9["utils"] -A["containerService.js"] -C["dockerClient.js"] -end -2-->4 -4-->6 -4-->7 -7-->6 -7-->A -7-->8 -A-->B -A-->C diff --git a/misc/dependencyGraphs/mermaid-notificationService.txt b/misc/dependencyGraphs/mermaid-notificationService.txt deleted file mode 100644 index dbfbd46c..00000000 --- a/misc/dependencyGraphs/mermaid-notificationService.txt +++ /dev/null @@ -1,37 +0,0 @@ -flowchart LR - -subgraph 0["routes"] -subgraph 1["notifications"] -2["routes.js"] -end -end -subgraph 3["utils"] -subgraph 4["notifications"] -5["_notify.js"] -6["discord.js"] -subgraph 7["data"] -8["template.js"] -end -9["email.js"] -A["pushbullet.js"] -B["pushover.js"] -C["slack.js"] -D["telegram.js"] -E["whatsapp.js"] -end -end -2-->5 -5-->6 -5-->9 -5-->A -5-->B -5-->C -5-->D -5-->E -6-->8 -9-->8 -A-->8 -B-->8 -C-->8 -D-->8 -E-->8 diff --git a/misc/entrypoint.sh b/misc/entrypoint.sh deleted file mode 100755 index 2008cdbd..00000000 --- a/misc/entrypoint.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash - -cat << EOF -Welcome to - - ###### ###### #### ### ### #### ######### ###### ######### - ### ### ### ### ### ### ### ### ### ### ### ### - ### ### ### ### ### ###### #### ### ### ### ### - ### ### ### ### ### ### ### #### ### ############ ### - ### ### ### ### ### ### ### #### ### ### ### ### - ###### ###### #### ### ### #### ### ### ### ### (API) - -Useful links: - -- Documentation: https://outline.itsnik.de/s/dockstat -- GitHub (Frontend): https://github.com/its4nik/dockstat -- GitHub (Backend): https://github.com/its4nik/dockstatapi -- API Documentation: http://localhost:7000/api-docs - -Summary: - -DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. - -EOF - -npm run start diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 00000000..30602eb0 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,6 @@ +{ + "ignore": ["src/logs", "**/fixtures/**", ".gitignore", "**/*.json"], + "execMap": { + "ts": "tsx" + } +} diff --git a/package-lock.json b/package-lock.json index f4dcffbf..641c0d3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,26 +9,44 @@ "version": "2", "license": "BSD 3-Clause License", "dependencies": { + "@types/dockerode": "^3.3.31", + "@types/supports-color": "^8.1.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", "bcrypt": "^5.1.1", - "child_process": "^1.0.2", + "chokidar": "^4.0.1", "cors": "^2.8.5", "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", - "js-yaml": "^4.1.0", + "https": "^1.0.0", + "ipaddr.js": "^2.2.0", "node-fetch": "^3.3.2", "nodemailer": "^6.9.16", - "python-shell": "^5.0.0", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "twilio": "^5.3.5", - "winston": "^3.15.0", - "yamljs": "^0.3.0" + "winston": "^3.15.0" }, "devDependencies": { + "@playwright/test": "^1.49.0", + "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/express-handlebars": "^5.3.1", + "@types/node": "^22.9.0", + "@types/node-fetch": "^2.6.12", + "@types/nodemailer": "^6.4.17", "dependency-cruiser": "^16.5.0", - "nodemon": "^3.1.7" + "nodemon": "^3.1.7", + "ora": "^8.1.1", + "pkg": "^5.8.1", + "ts-node": "^10.9.2", + "tsx": "^4.19.2", + "uglify-js": "^3.19.3" + }, + "engines": { + "npm": ">=10.8.2" } }, "node_modules/@apidevtools/json-schema-ref-parser": { @@ -75,6 +93,69 @@ "openapi-types": ">=7" } }, + "node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -90,6 +171,19 @@ "node": ">=0.1.90" } }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@dabh/diagnostics": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", @@ -101,199 +195,924 @@ "kuler": "^2.0.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, "license": "MIT", - "optional": true - }, - "node_modules/@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==", - "license": "MIT" - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "license": "BSD-3-Clause", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "deprecated": "This package is no longer supported.", - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@npmcli/fs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", - "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", - "license": "ISC", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, - "dependencies": { - "@gar/promisify": "^1.0.1", - "semver": "^7.3.5" + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "deprecated": "This functionality has been moved to @npmcli/fs", + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", "optional": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, + "os": [ + "darwin" + ], "engines": { - "node": ">=10" + "node": ">=18" } }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, "license": "MIT", "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 6" + "node": ">=18" } }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT" - }, - "node_modules/@types/triple-beam": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", - "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", - "license": "MIT" - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "license": "ISC" - }, - "node_modules/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==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">= 0.6" + "node": ">=18" } }, - "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.4.0" + "node": ">=18" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/acorn-jsx-walk": { - "version": "2.0.0", + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@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==", + "license": "MIT" + }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@playwright/test": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", + "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/bcrypt": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", + "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/docker-modem": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", + "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/dockerode": { + "version": "3.3.32", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz", + "integrity": "sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==", + "license": "MIT", + "dependencies": { + "@types/docker-modem": "*", + "@types/node": "*", + "@types/ssh2": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-handlebars": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@types/express-handlebars/-/express-handlebars-5.3.1.tgz", + "integrity": "sha512-DSzaERLO4gHb8AqnrL58jzSDyT0yDdl6HqDc+bGz1Hf0nrG1FK30nHGzv8NBEGR8QV9eUGB/YaE0Qj3NjF7siw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", + "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.17", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", + "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/ssh2": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", + "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "license": "MIT", + "dependencies": { + "@types/node": "^18.11.18" + } + }, + "node_modules/@types/ssh2/node_modules/@types/node": { + "version": "18.19.67", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", + "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/ssh2/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/@types/supports-color": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", + "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "license": "MIT" + }, + "node_modules/@types/swagger-jsdoc": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", + "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "license": "MIT" + }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz", + "integrity": "sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g==", + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC" + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/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, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-jsx-walk": { + "version": "2.0.0", "resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz", "integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==", "dev": true, @@ -406,6 +1225,26 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansi-styles/node_modules/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ansi-styles/node_modules/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==", + "dev": true, + "license": "MIT" + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -420,6 +1259,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -427,28 +1279,31 @@ "license": "ISC" }, "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", "license": "ISC", - "optional": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", @@ -456,6 +1311,16 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -475,17 +1340,17 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, - "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "node_modules/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, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" } }, "node_modules/balanced-match": { @@ -537,12 +1402,6 @@ "tweetnacl": "^0.14.3" } }, - "node_modules/bcrypt/node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -580,6 +1439,7 @@ "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -603,6 +1463,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -610,7 +1471,8 @@ "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.11", @@ -659,12 +1521,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/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": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, "node_modules/buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", @@ -678,6 +1534,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -712,26 +1569,16 @@ "node": ">= 10" } }, - "node_modules/cacache/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "optional": true, - "engines": { - "node": ">=10" - } - }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" }, "engines": { "node": ">= 0.4" @@ -740,6 +1587,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/call-me-maybe": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", @@ -747,81 +1607,41 @@ "license": "MIT" }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/child_process": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==", - "license": "ISC" - }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.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" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "license": "ISC" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } }, "node_modules/clean-stack": { "version": "2.2.0", @@ -833,6 +1653,47 @@ "node": ">=6" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -844,22 +1705,18 @@ } }, "node_modules/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==", - "dev": true, + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "color-name": "1.1.3" } }, "node_modules/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==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "license": "MIT" }, "node_modules/color-string": { @@ -881,21 +1738,6 @@ "color-support": "bin.js" } }, - "node_modules/color/node_modules/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==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -910,6 +1752,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -919,12 +1762,13 @@ } }, "node_modules/commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 6" + "node": ">=18" } }, "node_modules/concat-map": { @@ -955,6 +1799,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -974,6 +1819,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/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, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -1001,6 +1853,13 @@ "node": ">=10.0.0" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -1010,19 +1869,13 @@ "node": ">= 12" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -1061,6 +1914,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1077,6 +1931,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -1092,18 +1947,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/dependency-cruiser": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.5.0.tgz", - "integrity": "sha512-6IELC3qRumlwhnbPLmcOK6WWdiGPFBw9a+D8DUsnTFpZ81tEtkAud4OPmU3OJFcuWS5VpgvKlctFkby5XDsGzQ==", + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.7.0.tgz", + "integrity": "sha512-522LLjHINl9r0RIZ8/6s6TqIHTuEJG3XDU2WPSm9dG0rvLUYVyQwE9ID31tDFs4OOyEhdOPaqAaAG1jRv/Zwbg==", "dev": true, "license": "MIT", "dependencies": { - "acorn": "^8.13.0", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "acorn-jsx-walk": "^2.0.0", "acorn-loose": "^8.4.0", @@ -1123,8 +1979,8 @@ "safe-regex": "^2.1.1", "semver": "^7.6.3", "teamcity-service-messages": "^0.1.14", - "tsconfig-paths-webpack-plugin": "^4.1.0", - "watskeburt": "^4.1.0" + "tsconfig-paths-webpack-plugin": "^4.2.0", + "watskeburt": "^4.1.1" }, "bin": { "depcruise": "bin/dependency-cruise.mjs", @@ -1138,33 +1994,11 @@ "node": "^18.17||>=20" } }, - "node_modules/dependency-cruiser/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/dependency-cruiser/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -1179,6 +2013,29 @@ "node": ">=8" } }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/docker-modem": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", @@ -1220,19 +2077,25 @@ "node": ">=6.0.0" } }, - "node_modules/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==", - "license": "Apache-2.0", + "node_modules/dunder-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", + "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "call-bind-apply-helpers": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -1250,6 +2113,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1318,12 +2182,10 @@ "optional": true }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -1332,14 +2194,66 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", @@ -1354,6 +2268,7 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -1368,9 +2283,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -1392,7 +2307,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1407,6 +2322,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express-rate-limit": { @@ -1446,6 +2365,23 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-uri": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", @@ -1453,6 +2389,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -1505,6 +2451,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -1522,6 +2469,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -1529,7 +2477,8 @@ "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/fn.name": { "version": "1.1.0", @@ -1537,30 +2486,11 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, - "node_modules/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==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/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==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -1596,16 +2526,77 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/node_modules/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, + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/from2/node_modules/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, + "license": "MIT" + }, + "node_modules/from2/node_modules/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, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -1625,9 +2616,9 @@ "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1643,41 +2634,69 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", "license": "ISC", - "optional": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "wide-align": "^1.1.2" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" + } + }, + "node_modules/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, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz", + "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -1686,6 +2705,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", + "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1742,22 +2774,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/global-directory/node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "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" + }, "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1770,6 +2824,16 @@ "devOptional": true, "license": "ISC" }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1784,6 +2848,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -1791,21 +2856,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1823,6 +2878,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -1841,6 +2897,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -1867,6 +2924,12 @@ "node": ">= 6" } }, + "node_modules/https": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", + "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==", + "license": "ISC" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1894,6 +2957,7 @@ "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -1983,10 +3047,14 @@ "license": "ISC" }, "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/interpret": { "version": "3.1.1", @@ -1998,6 +3066,23 @@ "node": ">=10.13.0" } }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -2012,20 +3097,13 @@ "node": ">= 12" } }, - "node_modules/ip-address/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause", - "optional": true - }, "node_modules/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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 10" } }, "node_modules/is-arrayish": { @@ -2112,6 +3190,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -2154,6 +3245,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2173,12 +3284,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" - }, "node_modules/jsbn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", @@ -2186,6 +3291,19 @@ "license": "MIT", "optional": true }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2206,47 +3324,17 @@ "node": ">=6" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "license": "MIT", "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" + "universalify": "^2.0.0" }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, "node_modules/kleur": { @@ -2271,64 +3359,52 @@ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "license": "MIT" }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "license": "MIT" }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/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==", - "license": "MIT" - }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/logform": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz", - "integrity": "sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", "license": "MIT", "dependencies": { "@colors/colors": "1.6.0", @@ -2379,6 +3455,13 @@ "semver": "bin/semver.js" } }, + "node_modules/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, + "license": "ISC" + }, "node_modules/make-fetch-happen": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", @@ -2411,6 +3494,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -2435,10 +3519,21 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2448,10 +3543,38 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -2640,15 +3763,40 @@ "license": "MIT" }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, "node_modules/nan": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", - "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", "license": "MIT", "optional": true }, @@ -2680,9 +3828,9 @@ } }, "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "license": "MIT" }, "node_modules/node-domexception": { @@ -2747,6 +3895,59 @@ "node": ">= 10.12.0" } }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/nodemailer": { "version": "6.9.16", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", @@ -2778,11 +3979,62 @@ "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=10" + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, "node_modules/nopt": { @@ -2811,20 +4063,16 @@ } }, "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", "license": "ISC", - "optional": true, "dependencies": { - "are-we-there-yet": "^3.0.0", + "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", + "gauge": "^3.0.0", "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, "node_modules/object-assign": { @@ -2837,9 +4085,10 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -2851,6 +4100,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -2873,79 +4123,442 @@ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", "license": "MIT", "dependencies": { - "fn.name": "1.x.x" + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, + "node_modules/ora": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", + "integrity": "sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz", + "integrity": "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.19.0", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "7.1.1", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/pkg-fetch/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-fetch/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/pkg-fetch/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "node_modules/pkg-fetch/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dev": true, "license": "MIT", - "peer": true + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } }, - "node_modules/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==", + "node_modules/pkg/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "aggregate-error": "^3.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "node_modules/pkg/node_modules/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, + "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "node_modules/pkg/node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=0.10.0" + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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==", + "node_modules/pkg/node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "node_modules/pkg/node_modules/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, - "license": "ISC" + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "node_modules/playwright": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", + "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.49.0" + }, + "bin": { + "playwright": "cli.js" + }, "engines": { - "node": ">=8.6" + "node": ">=18" }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", + "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" } }, "node_modules/prebuild-install": { @@ -2974,6 +4587,23 @@ "node": ">=10" } }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -3022,11 +4652,14 @@ "node": ">= 0.10" } }, - "node_modules/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==", - "license": "MIT" + "node_modules/proxy-addr/node_modules/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==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } }, "node_modules/pstree.remy": { "version": "1.1.8", @@ -3036,28 +4669,20 @@ "license": "MIT" }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "node_modules/python-shell": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/python-shell/-/python-shell-5.0.0.tgz", - "integrity": "sha512-RUOOOjHLhgR1MIQrCtnEqz/HJ1RMZBIN+REnpSUrfft2bXqXy69fwJASVziWExfFXsR1bCY0TznnHooNsCo0/w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -3068,10 +4693,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/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==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3080,6 +4727,7 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -3105,6 +4753,12 @@ "rc": "cli.js" } }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3120,16 +4774,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, "engines": { - "node": ">=8.10.0" + "node": ">= 14.16.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -3155,6 +4809,16 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3183,6 +4847,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/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, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -3193,6 +4897,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3200,13 +4915,37 @@ "deprecated": "Rimraf versions prior to v4 are no longer supported", "license": "ISC", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" } }, "node_modules/safe-buffer": { @@ -3240,9 +4979,9 @@ } }, "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -3254,12 +4993,6 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, - "node_modules/scmp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", - "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", - "license": "BSD-3-Clause" - }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -3276,6 +5009,7 @@ "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -3299,6 +5033,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -3306,25 +5041,23 @@ "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -3345,6 +5078,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -3360,12 +5094,14 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -3459,6 +5195,16 @@ "dev": true, "license": "MIT" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -3507,10 +5253,11 @@ "license": "ISC" }, "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "license": "BSD-3-Clause" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause", + "optional": true }, "node_modules/sqlite3": { "version": "5.1.7", @@ -3536,10 +5283,16 @@ } } }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/ssh2": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.15.0.tgz", - "integrity": "sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", + "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", "hasInstallScript": true, "dependencies": { "asn1": "^0.2.6", @@ -3549,8 +5302,8 @@ "node": ">=10.16.0" }, "optionalDependencies": { - "cpu-features": "~0.0.9", - "nan": "^2.18.0" + "cpu-features": "~0.0.10", + "nan": "^2.20.0" } }, "node_modules/ssri": { @@ -3579,10 +5332,67 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/stream-meter/node_modules/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, + "license": "MIT", + "dependencies": { + "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" + } + }, + "node_modules/stream-meter/node_modules/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, + "license": "MIT" + }, + "node_modules/stream-meter/node_modules/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, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -3683,6 +5493,15 @@ "node": ">=12.0.0" } }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/swagger-jsdoc/node_modules/glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -3717,10 +5536,13 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.17.14", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz", - "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==", - "license": "Apache-2.0" + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", + "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } }, "node_modules/swagger-ui-express": { "version": "5.0.1", @@ -3776,6 +5598,12 @@ "tar-stream": "^2.0.0" } }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, "node_modules/tar-stream": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", @@ -3792,15 +5620,6 @@ "node": ">=6" } }, - "node_modules/tar/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -3823,6 +5642,16 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3840,6 +5669,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } @@ -3869,6 +5699,50 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", @@ -3885,20 +5759,96 @@ } }, "node_modules/tsconfig-paths-webpack-plugin": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.1.0.tgz", - "integrity": "sha512-xWFISjviPydmtmgeUAuXp4N1fky+VCtfhOkDUFIv5ea7p4wuTomI4QTrXvFBX2S4jZsmyTSrStQl+E+4w+RzxA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz", + "integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", + "tapable": "^2.2.1", "tsconfig-paths": "^4.1.2" }, "engines": { "node": ">=10.13.0" } }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsx": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", + "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -3917,28 +5867,11 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, - "node_modules/twilio": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.3.5.tgz", - "integrity": "sha512-f/sA1Yd6TyIzfcq0u4QDGU+93afwswsJB+rf3T08tvBAMobBDVR3DfGREwJr5jp8xUic0qWa7GbJidk16NA4bg==", - "license": "MIT", - "dependencies": { - "axios": "^1.7.4", - "dayjs": "^1.11.9", - "https-proxy-agent": "^5.0.0", - "jsonwebtoken": "^9.0.2", - "qs": "^6.9.4", - "scmp": "^2.1.0", - "xmlbuilder": "^13.0.2" - }, - "engines": { - "node": ">=14.0" - } - }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -3947,6 +5880,34 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", @@ -3954,6 +5915,12 @@ "dev": true, "license": "MIT" }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, "node_modules/unique-filename": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", @@ -3974,10 +5941,21 @@ "imurmurhash": "^0.1.4" } }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3997,6 +5975,13 @@ "node": ">= 0.4.0" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/validator": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", @@ -4016,9 +6001,9 @@ } }, "node_modules/watskeburt": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-4.1.0.tgz", - "integrity": "sha512-KkY5H51ajqy9HYYI+u9SIURcWnqeVVhdH0I+ab6aXPGHfZYxgRCwnR6Lm3+TYB6jJVt5jFqw4GAKmwf1zHmGQw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-4.2.2.tgz", + "integrity": "sha512-AOCg1UYxWpiHW1tUwqpJau8vzarZYTtzl2uu99UptBmbzx6kOzCGMfRLF6KIRX4PYekmryn89MzxlRNkL66YyA==", "dev": true, "license": "MIT", "bin": { @@ -4079,34 +6064,34 @@ } }, "node_modules/winston": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.15.0.tgz", - "integrity": "sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==", + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", "async": "^3.2.3", "is-stream": "^2.0.0", - "logform": "^2.6.0", + "logform": "^2.7.0", "one-time": "^1.0.0", "readable-stream": "^3.4.0", "safe-stable-stringify": "^2.3.1", "stack-trace": "0.0.x", "triple-beam": "^1.3.0", - "winston-transport": "^4.7.0" + "winston-transport": "^4.9.0" }, "engines": { "node": ">= 12.0.0" } }, "node_modules/winston-transport": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.1.tgz", - "integrity": "sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", "license": "MIT", "dependencies": { - "logform": "^2.6.1", + "logform": "^2.7.0", "readable-stream": "^3.6.2", "triple-beam": "^1.3.0" }, @@ -4114,19 +6099,38 @@ "node": ">= 12.0.0" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/xmlbuilder": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", - "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", - "license": "MIT", + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", "engines": { - "node": ">=6.0" + "node": ">=10" } }, "node_modules/yallist": { @@ -4144,18 +6148,43 @@ "node": ">= 6" } }, - "node_modules/yamljs": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", - "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "glob": "^7.0.5" + "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" }, - "bin": { - "json2yaml": "bin/json2yaml", - "yaml2json": "bin/yaml2json" + "engines": { + "node": ">=10" + } + }, + "node_modules/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, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/z-schema": { diff --git a/package.json b/package.json index b1c3b7c4..78ac9460 100644 --- a/package.json +++ b/package.json @@ -2,43 +2,68 @@ "name": "dockstatapi", "version": "2", "description": "API for docker hosts using dockerode", - "main": "server.js", + "main": "src/server.ts", "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "offline": "OFFLINE=true nodemon server.js", - "dep": "bash ./utils/createDependencyGraph.sh" + "start": "tsx src/server.ts", + "start:build": "npx tsc && node dist/server.js", + "dev": "nodemon", + "dev:trace": "nodemon --trace-uncaught --trace-warnings", + "dep": "bash ./src/utils/createDependencyGraph.sh", + "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh", + "build": "npx tsc", + "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", + "mini": "bash ./src/misc/minifyDist.sh" }, "keywords": [], "author": "Its4Nik", "license": "BSD 3-Clause License", "dependencies": { + "@types/dockerode": "^3.3.31", + "@types/supports-color": "^8.1.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", "bcrypt": "^5.1.1", - "child_process": "^1.0.2", + "chokidar": "^4.0.1", "cors": "^2.8.5", "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", - "js-yaml": "^4.1.0", + "https": "^1.0.0", + "ipaddr.js": "^2.2.0", "node-fetch": "^3.3.2", "nodemailer": "^6.9.16", - "python-shell": "^5.0.0", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "twilio": "^5.3.5", - "winston": "^3.15.0", - "yamljs": "^0.3.0" + "winston": "^3.15.0" }, "devDependencies": { + "@playwright/test": "^1.49.0", + "@types/bcrypt": "^5.0.2", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", + "@types/express-handlebars": "^5.3.1", + "@types/node": "^22.9.0", + "@types/node-fetch": "^2.6.12", + "@types/nodemailer": "^6.4.17", "dependency-cruiser": "^16.5.0", - "nodemon": "^3.1.7" + "nodemon": "^3.1.7", + "ora": "^8.1.1", + "pkg": "^5.8.1", + "ts-node": "^10.9.2", + "tsx": "^4.19.2", + "uglify-js": "^3.19.3" }, "nodemonConfig": { "ignore": [ - "**/logs/**", - "**/data/**" + "**/data/**", + "**/*.json", + ".gitignore" ], "delay": 2500 - } + }, + "engines": { + "npm": ">=10.8.2" + }, + "repository": "git@github.com:Its4Nik/dockstatapi.git" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..2c33a93e --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,37 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + timeout: 300000, + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + trace: 'on-first-retry', + }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + ], + + webServer: { + command: 'npm run start', + url: 'http://127.0.0.1:9876', + reuseExistingServer: true + }, +}); diff --git a/routes/auth/routes.js b/routes/auth/routes.js deleted file mode 100644 index bbc20d4b..00000000 --- a/routes/auth/routes.js +++ /dev/null @@ -1,146 +0,0 @@ -const express = require("express"); -const bcrypt = require("bcrypt"); -const fs = require("fs"); -const path = require("path"); -const logger = require("../../utils/logger"); -const router = express.Router(); -const passwordFile = path.join(__dirname, "../../middleware/password.json"); -const passwordBool = path.join(__dirname, "../../middleware/usePassword.txt"); -const saltRounds = 10; - -function setTrue() { - fs.writeFile(passwordBool, "true", "utf8", (err) => { - if (err) { - logger.error("Error writing to the file:", err); - return; - } - logger.info(`Status "true" has been written to the file.`); - }); -} - -function setFalse() { - fs.writeFile(passwordBool, "false", "utf8", (err) => { - if (err) { - logger.error("Error writing to the file:", err); - return; - } - logger.info(`Status "false" has been written to the file.`); - }); -} - -/** - * @swagger - * /auth/enable: - * post: - * summary: Enable authentication by setting a password - * tags: [Authentication] - * parameters: - * - name: password - * in: query - * required: true - * responses: - * 200: - * description: Authentication enabled. - * 400: - * description: Password is required. - * 500: - * description: Error saving password. - */ -router.post("/enable", (req, res) => { - fs.readFile(passwordBool, "utf8", (err, data) => { - const password = req.query.password; - if (err) { - logger.error("Error reading the file:", err); - return; - } - - const isAuthEnabled = data.trim() === "true"; - if (isAuthEnabled) { - logger.error( - "Passowrd Authentication is already enabled, please dactivate it first", - ); - return res.status(401).json({ - message: - "Passowrd Authentication is already enabled, please dactivate it first", - }); - } - - if (!password) { - return res.status(400).json({ message: "Password is required" }); - } - - bcrypt.genSalt(saltRounds, (err, salt) => { - if (err) { - logger.error("Error generating salt"); - return res.status(500).json({ message: "Error generating salt" }); - } - - bcrypt.hash(password, salt, (err, hash) => { - if (err) { - logger.error("Error hashing password"); - return res.status(500).json({ message: "Error hashing password" }); - } - - const passwordData = { hash, salt }; - fs.writeFile(passwordFile, JSON.stringify(passwordData), (err) => { - if (err) { - return res.status(500).json({ message: "Error saving password" }); - } - setTrue(); - res.json({ message: "Authentication enabled" }); - }); - }); - }); - }); -}); - -/** - * @swagger - * /auth/disable: - * post: - * summary: Disable authentication by providing the existing password - * tags: [Authentication] - * parameters: - * - name: password - * in: query - * required: true - * responses: - * 200: - * description: Authentication disabled. - * 400: - * description: Password is required. - * 401: - * description: Invalid password. - * 500: - * description: Error disabling authentication. - */ -router.post("/disable", (req, res) => { - const password = req.query.password; - if (!password) { - logger.error("Password is required!"); - return res.status(400).json({ message: "Password is required" }); - } - - fs.readFile(passwordFile, "utf8", (err, data) => { - if (err) { - logger.error("Error reading password"); - return res.status(500).json({ message: "Error reading password" }); - } - - const storedData = JSON.parse(data); - bcrypt.compare(password, storedData.hash, (err, result) => { - if (err) { - logger.error("Error validating password"); - return res.status(500).json({ message: "Error validating password" }); - } - if (!result) { - logger.error("Invalid password"); - return res.status(401).json({ message: "Invalid password" }); - } - setFalse(); - res.json({ message: "Authentication disabled" }); - }); - }); -}); - -module.exports = router; diff --git a/routes/data/routes.js b/routes/data/routes.js deleted file mode 100644 index adce8d79..00000000 --- a/routes/data/routes.js +++ /dev/null @@ -1,111 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const db = require("../../config/db"); -const logger = require("../../utils/logger"); - -function formatRows(rows) { - return rows.reduce((acc, row, index) => { - acc[index] = JSON.parse(row.info); - return acc; - }, {}); -} - -/** - * @swagger - * /data/latest: - * get: - * summary: Retrieve the latest entry from the database - * tags: [Database queries] - * responses: - * 200: - * description: A JSON object containing the latest entry's 'info' data. - * content: - * application/json: - * schema: - * type: object - * example: - * name: "Container A" - * id: "abcd1234" - * cpu_usage: 30 - * mem_usage: 2048 - */ -router.get("/latest", (req, res) => { - db.get( - "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", - (err, row) => { - if (err) { - logger.error("Error fetching latest data:", err.message); - return res.status(500).json({ error: "Internal server error" }); - } - res.json(JSON.parse(row.info)); - }, - ); -}); - -/** - * @swagger - * /data/time/24h: - * get: - * summary: Retrieve entries from the last 24 hours from the database - * tags: [Database queries] - * responses: - * 200: - * description: A numbered array of 'info' JSON objects from the last 24 hours. - * content: - * application/json: - * schema: - * type: object - * example: - * 0: - * name: "Container A" - * id: "abcd1234" - * cpu_usage: 30 - * mem_usage: 2048 - * 1: - * name: "Container B" - * id: "efgh5678" - * cpu_usage: 45 - * mem_usage: 3072 - */ -router.get("/time/24h", (req, res) => { - const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); - db.all( - "SELECT info FROM data WHERE timestamp >= ?", - [oneDayAgo], - (err, rows) => { - if (err) { - logger.error("Error fetching data from last 24 hours:", err.message); - return res.status(500).json({ error: "Internal server error" }); - } - res.json(formatRows(rows)); - }, - ); -}); - -/** - * @swagger - * /data/clear: - * delete: - * summary: Clear all entries from the database - * tags: [Database queries] - * responses: - * 200: - * description: A message indicating whether the database was cleared successfully. - * content: - * application/json: - * schema: - * type: object - * example: - * message: "Database cleared successfully." - */ -router.delete("/clear", (req, res) => { - db.run("DELETE FROM data", (err) => { - if (err) { - logger.error("Error clearing the database:", err.message); - return res.status(500).json({ error: "Internal server error" }); - } - res.json({ message: "Database cleared successfully" }); - }); -}); - -module.exports = router; diff --git a/routes/setter/routes.js b/routes/setter/routes.js deleted file mode 100644 index 24ae2ad9..00000000 --- a/routes/setter/routes.js +++ /dev/null @@ -1,145 +0,0 @@ -const { - setFetchInterval, - parseInterval, -} = require("../../controllers/scheduler"); -const express = require("express"); -const router = express.Router(); -const path = require("path"); -const fs = require("fs"); -const logger = require("../../utils/logger"); - -/** - * @swagger - * /conf/addHost: - * put: - * summary: Add a new host to the Docker configuration - * tags: [Configuration] - * parameters: - * - name: name - * in: query - * required: true - * description: The name of the new host. - * - name: url - * in: query - * required: true - * description: The URL of the new host. - * - name: port - * in: query - * required: true - * description: The port of the new host. - * responses: - * 200: - * description: Host added successfully. - * 400: - * description: Bad request, invalid input. - * 500: - * description: An error occurred while adding the host. - */ -router.put("/addHost", async (req, res) => { - const name = req.query.name; - const url = req.query.url; - const port = req.query.port; - const configPath = path.join(__dirname, "../../config/dockerConfig.json"); - - if (!name || !url || !port) { - return res.status(400).json({ error: "Name, Port and URL are required." }); - } - - try { - const rawData = fs.readFileSync(configPath); - const config = JSON.parse(rawData); - - // Check for existing host - if (config.hosts.some((host) => host.name === name)) { - return res.status(400).json({ error: "Host already exists." }); - } - - config.hosts.push({ name, url, port }); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - logger.info(`Added new host: ${name}`); - res.status(200).json({ message: "Host added successfully." }); - } catch (error) { - logger.error("Error adding host: " + error.message); - res.status(500).json({ error: "Failed to add host." }); - } -}); - -/** - * @swagger - * /conf/scheduler: - * put: - * summary: Set fetch interval for data fetching - * tags: [Configuration] - * parameters: - * - name: interval - * in: query - * required: true - * description: The new interval for fetching data, e.g., "6h 20m", "300s". - * responses: - * 200: - * description: Fetch interval set successfully. - * 400: - * description: Invalid interval format or out of range. - */ -router.put("/scheduler", (req, res) => { - const interval = req.query.interval; - const newInterval = parseInterval(interval); - - if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { - return res - .status(400) - .json({ error: "Interval must be between 5 minutes and 6 hours." }); - } - - setFetchInterval(newInterval); - res.json({ message: `Fetch interval set to ${interval}.` }); -}); - -/** - * @swagger - * /conf/removeHost: - * delete: - * summary: Remove a host from the Docker configuration - * tags: [Configuration] - * parameters: - * - name: hostName - * in: query - * required: true - * description: The name of the host to remove. - * responses: - * 200: - * description: Host removed successfully. - * 404: - * description: Host not found. - * 500: - * description: An error occurred while removing the host. - */ -router.delete("/removeHost", async (req, res) => { - const hostName = req.query.hostName; - const configPath = path.join(__dirname, "../../config/dockerConfig.json"); - - if (!hostName) { - return res.status(400).json({ error: "Host name is required." }); - } - - try { - const rawData = fs.readFileSync(configPath); - const config = JSON.parse(rawData); - - // Check for existing host - const hostIndex = config.hosts.findIndex((host) => host.name === hostName); - if (hostIndex === -1) { - return res.status(404).json({ error: "Host not found." }); - } - - config.hosts.splice(hostIndex, 1); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - logger.info(`Removed host: ${hostName}`); - res.status(200).json({ message: "Host removed successfully." }); - } catch (error) { - logger.error("Error removing host: " + error.message); - res.status(500).json({ error: "Failed to remove host." }); - } -}); - -module.exports = router; diff --git a/server.js b/server.js deleted file mode 100644 index d383cde9..00000000 --- a/server.js +++ /dev/null @@ -1,49 +0,0 @@ -const express = require("express"); -const router = express.Router(); -const app = express(); - -// Utility: -const swaggerDocs = require("./swagger/swaggerDocs"); -const logger = require("./utils/logger"); - -// Routes: -const api = require("./routes/getter/routes"); -const conf = require("./routes/setter/routes"); -const auth = require("./routes/auth/routes"); -const data = require("./routes/data/routes"); -const frontend = require("./routes/frontendController/routes"); -const notificationService = require("./routes/notifications/routes"); - -// Middleware: -const authMiddleware = require("./middleware/authMiddleware"); -const { limiter } = require("./middleware/rateLimiter"); - -// Controllers -const { scheduleFetch } = require("./controllers/scheduler"); - -const PORT = "7070"; - -app.use(express.json()); - -app.use("/api-docs", (req, res, next) => next()); - -swaggerDocs(app); -scheduleFetch(); - -// Routes -app.use("/api", limiter, authMiddleware, api); -app.use("/conf", limiter, authMiddleware, conf); -app.use("/auth", limiter, authMiddleware, auth); -app.use("/data", limiter, authMiddleware, data); -app.use("/frontend", limiter, authMiddleware, frontend); -app.use("/notification-service", limiter, authMiddleware, notificationService); - -// Default route -router.get("/", (req, res) => { - res.redirect("/api-docs"); -}); - -app.listen(PORT, () => { - logger.info(`Server is running on http://localhost:${PORT}`); - logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs`); -}); diff --git a/.dependency-cruiser.js b/src/.dependency-cruiser.cjs similarity index 66% rename from .dependency-cruiser.js rename to src/.dependency-cruiser.cjs index 07df12bf..d734a682 100644 --- a/.dependency-cruiser.js +++ b/src/.dependency-cruiser.cjs @@ -2,87 +2,83 @@ module.exports = { forbidden: [ { - name: 'no-circular', - severity: 'warn', + name: "no-circular", + severity: "warn", comment: - 'This dependency is part of a circular relationship. You might want to revise ' + - 'your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ', + "This dependency is part of a circular relationship. You might want to revise " + + "your solution (i.e. use dependency inversion, make sure the modules have a single responsibility) ", from: {}, to: { - circular: true - } + circular: true, + }, }, { - name: 'no-orphans', + name: "no-orphans", comment: "This is an orphan module - it's likely not used (anymore?). Either use it or " + "remove it. If it's logical this module is an orphan (i.e. it's a config file), " + "add an exception for it in your dependency-cruiser configuration. By default " + "this rule does not scrutinize dot-files (e.g. .eslintrc.js), TypeScript declaration " + "files (.d.ts), tsconfig.json and some of the babel and webpack configs.", - severity: 'warn', + severity: "warn", from: { orphan: true, pathNot: [ - '(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$', // dot files - '[.]d[.]ts$', // TypeScript declaration files - '(^|/)tsconfig[.]json$', // TypeScript config - '(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$' // other configs - ] + "(^|/)[.][^/]+[.](?:js|cjs|mjs|ts|cts|mts|json)$", // dot files + "[.]d[.]ts$", // TypeScript declaration files + "(^|/)tsconfig[.]json$", // TypeScript config + "(^|/)(?:babel|webpack)[.]config[.](?:js|cjs|mjs|ts|cts|mts|json)$", // other configs + ], }, to: {}, }, { - name: 'no-deprecated-core', + name: "no-deprecated-core", comment: - 'A module depends on a node core module that has been deprecated. Find an alternative - these are ' + + "A module depends on a node core module that has been deprecated. Find an alternative - these are " + "bound to exist - node doesn't deprecate lightly.", - severity: 'warn', + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'core' - ], + dependencyTypes: ["core"], path: [ - '^v8/tools/codemap$', - '^v8/tools/consarray$', - '^v8/tools/csvparser$', - '^v8/tools/logreader$', - '^v8/tools/profile_view$', - '^v8/tools/profile$', - '^v8/tools/SourceMap$', - '^v8/tools/splaytree$', - '^v8/tools/tickprocessor-driver$', - '^v8/tools/tickprocessor$', - '^node-inspect/lib/_inspect$', - '^node-inspect/lib/internal/inspect_client$', - '^node-inspect/lib/internal/inspect_repl$', - '^async_hooks$', - '^punycode$', - '^domain$', - '^constants$', - '^sys$', - '^_linklist$', - '^_stream_wrap$' + "^v8/tools/codemap$", + "^v8/tools/consarray$", + "^v8/tools/csvparser$", + "^v8/tools/logreader$", + "^v8/tools/profile_view$", + "^v8/tools/profile$", + "^v8/tools/SourceMap$", + "^v8/tools/splaytree$", + "^v8/tools/tickprocessor-driver$", + "^v8/tools/tickprocessor$", + "^node-inspect/lib/_inspect$", + "^node-inspect/lib/internal/inspect_client$", + "^node-inspect/lib/internal/inspect_repl$", + "^async_hooks$", + "^punycode$", + "^domain$", + "^constants$", + "^sys$", + "^_linklist$", + "^_stream_wrap$", ], - } + }, }, { - name: 'not-to-deprecated', + name: "not-to-deprecated", comment: - 'This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later ' + - 'version of that module, or find an alternative. Deprecated modules are a security risk.', - severity: 'warn', + "This module uses a (version of an) npm module that has been deprecated. Either upgrade to a later " + + "version of that module, or find an alternative. Deprecated modules are a security risk.", + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'deprecated' - ] - } + dependencyTypes: ["deprecated"], + }, }, { - name: 'no-non-package-json', - severity: 'error', + name: "no-non-package-json", + severity: "error", comment: "This module depends on an npm package that isn't in the 'dependencies' section of your package.json. " + "That's problematic as the package either (1) won't be available on live (2 - worse) will be " + @@ -90,84 +86,75 @@ module.exports = { "in your package.json.", from: {}, to: { - dependencyTypes: [ - 'npm-no-pkg', - 'npm-unknown' - ] - } + dependencyTypes: ["npm-no-pkg", "npm-unknown"], + }, }, { - name: 'not-to-unresolvable', + name: "not-to-unresolvable", comment: "This module depends on a module that cannot be found ('resolved to disk'). If it's an npm " + - 'module: add it to your package.json. In all other cases you likely already know what to do.', - severity: 'error', + "module: add it to your package.json. In all other cases you likely already know what to do.", + severity: "error", from: {}, to: { - couldNotResolve: true - } + couldNotResolve: true, + }, }, { - name: 'no-duplicate-dep-types', + name: "no-duplicate-dep-types", comment: "Likely this module depends on an external ('npm') package that occurs more than once " + "in your package.json i.e. bot as a devDependencies and in dependencies. This will cause " + "maintenance problems later on.", - severity: 'warn', + severity: "warn", from: {}, to: { moreThanOneDependencyType: true, - // as it's pretty common to have a type import be a type only import + // as it's pretty common to have a type import be a type only import // _and_ (e.g.) a devDependency - don't consider type-only dependency // types for this rule - dependencyTypesNot: ["type-only"] - } + dependencyTypesNot: ["type-only"], + }, }, /* rules you might want to tweak for your specific situation: */ - + { - name: 'not-to-spec', + name: "not-to-spec", comment: - 'This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. ' + + "This module depends on a spec (test) file. The sole responsibility of a spec file is to test code. " + "If there's something in a spec that's of use to other modules, it doesn't have that single " + - 'responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.', - severity: 'error', + "responsibility anymore. Factor it out into (e.g.) a separate utility/ helper or a mock.", + severity: "error", from: {}, to: { - path: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' - } + path: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", + }, }, { - name: 'not-to-dev-dep', - severity: 'error', + name: "not-to-dev-dep", + severity: "error", comment: "This module depends on an npm package from the 'devDependencies' section of your " + - 'package.json. It looks like something that ships to production, though. To prevent problems ' + + "package.json. It looks like something that ships to production, though. To prevent problems " + "with npm packages that aren't there on production declare it (only!) in the 'dependencies'" + - 'section of your package.json. If this module is development only - add it to the ' + - 'from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration', + "section of your package.json. If this module is development only - add it to the " + + "from.pathNot re of the not-to-dev-dep rule in the dependency-cruiser configuration", from: { - path: '^(\./)', - pathNot: '[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$' + path: "^(./)", + pathNot: "[.](?:spec|test)[.](?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$", }, to: { - dependencyTypes: [ - 'npm-dev', - ], + dependencyTypes: ["npm-dev"], // type only dependencies are not a problem as they don't end up in the // production code or are ignored by the runtime. - dependencyTypesNot: [ - 'type-only' - ], - pathNot: [ - 'node_modules/@types/' - ] - } + dependencyTypesNot: ["type-only"], + pathNot: ["node_modules/@types/"], + }, }, { - name: 'optional-deps-used', - severity: 'info', + name: "optional-deps-used", + severity: "info", comment: "This module depends on an npm package that is declared as an optional dependency " + "in your package.json. As this makes sense in limited situations only, it's flagged here. " + @@ -175,33 +162,28 @@ module.exports = { "dependency-cruiser configuration.", from: {}, to: { - dependencyTypes: [ - 'npm-optional' - ] - } + dependencyTypes: ["npm-optional"], + }, }, { - name: 'peer-deps-used', + name: "peer-deps-used", comment: "This module depends on an npm package that is declared as a peer dependency " + "in your package.json. This makes sense if your package is e.g. a plugin, but in " + "other cases - maybe not so much. If the use of a peer dependency is intentional " + "add an exception to your dependency-cruiser configuration.", - severity: 'warn', + severity: "warn", from: {}, to: { - dependencyTypes: [ - 'npm-peer' - ] - } - } + dependencyTypes: ["npm-peer"], + }, + }, ], options: { - /* Which modules not to follow further when encountered */ doNotFollow: { /* path: an array of regular expressions in strings to match against */ - path: ['node_modules'] + path: ["../node_modules"], }, /* Which modules to exclude */ @@ -220,15 +202,15 @@ module.exports = { module systems it knows of. It's the default because it's the safe option It might come at a performance penalty, though. moduleSystems: ['amd', 'cjs', 'es6', 'tsd'] - + As in practice only commonjs ('cjs') and ecmascript modules ('es6') are widely used, you can limit the moduleSystems to those. */ - + // moduleSystems: ['cjs', 'es6'], /* prefix for links in html and svg output (e.g. 'https://github.com/you/yourrepo/blob/main/' - to open it on your online repo or `vscode://file/${process.cwd()}/` to + to open it on your online repo or `vscode://file/${process.cwd()}/` to open it in visual studio code), */ // prefix: `vscode://file/${process.cwd()}/`, @@ -238,7 +220,7 @@ module.exports = { "specify": for each dependency identify whether it only exists before compilation or also after */ // tsPreCompilationDeps: false, - + /* list of extensions to scan that aren't javascript or compile-to-javascript. Empty by default. Only put extensions in here that you want to take into account that are _not_ parsable. @@ -262,9 +244,9 @@ module.exports = { dependency-cruiser's current working directory). When not provided defaults to './tsconfig.json'. */ - // tsConfig: { - // fileName: 'tsconfig.json' - // }, + //tsConfig: { + //fileName: "../tsconfig.json", + //}, /* Webpack configuration to use to get resolve options from. @@ -273,7 +255,7 @@ module.exports = { to './webpack.conf.js'. The (optional) `env` and `arguments` attributes contain the parameters - to be passed if your webpack config is a function and takes them (see + to be passed if your webpack config is a function and takes them (see webpack documentation for details) */ // webpackConfig: { @@ -295,7 +277,7 @@ module.exports = { a hack. */ // exoticRequireStrings: [], - + /* options to pass on to enhanced-resolve, the package dependency-cruiser uses to resolve module references to disk. The values below should be suitable for most situations @@ -304,7 +286,7 @@ module.exports = { there will override the ones specified here. */ enhancedResolveOptions: { - /* What to consider as an 'exports' field in package.jsons */ + /* What to consider as an 'exports' field in package.jsons */ exportsFields: ["exports"], /* List of conditions to check for in the exports field. Only works when the 'exportsFields' array is non-empty. @@ -314,22 +296,18 @@ module.exports = { The extensions, by default are the same as the ones dependency-cruiser can access (run `npx depcruise --info` to see which ones that are in _your_ environment). If that list is larger than you need you can pass - the extensions you actually use (e.g. [".js", ".jsx"]). This can speed + the extensions you actually use (e.g. ["", ".jsx"]). This can speed up module resolution, which is the most expensive step. */ - // extensions: [".js", ".jsx", ".ts", ".tsx", ".d.ts"], + extensions: ["", ".jsx", ".ts", ".tsx"], /* What to consider a 'main' field in package.json */ - - // if you migrate to ESM (or are in an ESM environment already) you will want to - // have "module" in the list of mainFields, like so: - // mainFields: ["module", "main", "types", "typings"], - mainFields: ["main", "types", "typings"], + mainFields: ["module", "main", "types", "typings"], /* A list of alias fields in package.jsons See [this specification](https://github.com/defunctzombie/package-browser-field-spec) and the webpack [resolve.alias](https://webpack.js.org/configuration/resolve/#resolvealiasfields) - documentation - + documentation + Defaults to an empty array (= don't use alias fields). */ // aliasFields: ["browser"], @@ -341,21 +319,21 @@ module.exports = { collapses everything in node_modules to one folder deep so you see the external modules, but their innards. */ - collapsePattern: 'node_modules/(?:@[^/]+/[^/]+|[^/]+)', + collapsePattern: "node_modules/(?:@[^/]+/[^/]+|[^/]+)", /* Options to tweak the appearance of your graph.See https://github.com/sverweij/dependency-cruiser/blob/main/doc/options-reference.md#reporteroptions for details and some examples. If you don't specify a theme dependency-cruiser falls back to a built-in one. */ - // theme: { - // graph: { - // /* splines: "ortho" gives straight lines, but is slow on big graphs - // splines: "true" gives bezier curves (fast, not as nice as ortho) - // */ - // splines: "true" - // }, - // } + theme: { + graph: { + /* splines: "ortho" gives straight lines, but is slow on big graphs + splines: "true" gives bezier curves (fast, not as nice as ortho) + */ + ortho: "true", + }, + }, }, archi: { /* pattern of modules that can be consolidated in the high level @@ -363,7 +341,8 @@ module.exports = { dependency graph reporter (`archi`) you probably want to tweak this collapsePattern to your situation. */ - collapsePattern: '^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)', + collapsePattern: + "^(?:packages|src|lib(s?)|app(s?)|bin|test(s?)|spec(s?))/[^/]+|node_modules/(?:@[^/]+/[^/]+|[^/]+)", /* Options to tweak the appearance of your graph. If you don't specify a theme for 'archi' dependency-cruiser will use the one specified in the @@ -371,10 +350,10 @@ module.exports = { */ // theme: { }, }, - "text": { - "highlightFocused": true + text: { + highlightFocused: true, }, - } - } + }, + }, }; -// generated: dependency-cruiser@16.5.0 on 2024-10-31T20:09:59.974Z +// generated: dependency-cruiser@16.5.0 on 2024-11-08T20:57:37.261Z diff --git a/src/config/db.ts b/src/config/db.ts new file mode 100644 index 00000000..93972131 --- /dev/null +++ b/src/config/db.ts @@ -0,0 +1,30 @@ +import sqlite3 from "sqlite3"; +import logger from "../utils/logger"; + +const dbPath: string = "./src/data/database.db"; + +const db: sqlite3.Database = new sqlite3.Database( + dbPath, + (err: Error | null) => { + if (err) { + logger.error("Error opening database:", err.message); + } else { + db.run( + `CREATE TABLE IF NOT EXISTS data ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + info TEXT NOT NULL, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP + )`, + (tableErr: Error | null) => { + if (tableErr) { + logger.error("Error creating table:", tableErr.message); + } else { + logger.info("Database created / opened successfully"); + } + }, + ); + } + }, +); + +export default db; diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts new file mode 100644 index 00000000..520d3efd --- /dev/null +++ b/src/config/hostsystem.ts @@ -0,0 +1,61 @@ +import fs from "fs"; +import logger from "../utils/logger"; +import os from "os"; + +const userConf = "./src/data/user.conf"; +const inDocker: boolean = !!process.env.RUNNING_IN_DOCKER; +const version: string = process.env.VERSION || "unknown"; + +function writeUserConf() { + let previousConfig = null; + let shouldRewriteConfig = false; + + const installationDetails = { + installedAt: new Date().toISOString(), + backendVersion: version, + inDocker: inDocker, + installedBy: os.userInfo().username, + platform: os.platform(), + arch: os.arch(), + }; + + if (fs.existsSync(userConf)) { + try { + previousConfig = JSON.parse(fs.readFileSync(userConf, "utf-8")); + if (previousConfig.backendVersion !== version) { + shouldRewriteConfig = true; + logger.debug( + "Version change detected. Rewriting configuration file...", + ); + } else { + logger.debug("No version change detected. Skipping re-initialization."); + } + } catch (error) { + logger.error( + "Error reading the configuration file. Rewriting it...", + error, + ); + shouldRewriteConfig = true; + } + } else { + logger.debug("Configuration file not found. Creating a new one..."); + shouldRewriteConfig = true; + } + + if (shouldRewriteConfig) { + fs.writeFileSync(userConf, JSON.stringify(installationDetails, null, 2)); + logger.debug("Configuration file created/updated:", userConf); + } + + const startDetails = { + startedAt: new Date().toISOString(), + backendVersion: version, + }; + + logger.info("Starting the server..."); + logger.info( + `At: ${startDetails.startedAt} - Version: ${startDetails.backendVersion} - Docker: ${installationDetails.inDocker} - Installed as: ${installationDetails.installedBy} - Platform: ${installationDetails.platform} - Arch: ${installationDetails.arch}`, + ); +} + +export default writeUserConf; diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts new file mode 100644 index 00000000..7d34f035 --- /dev/null +++ b/src/config/loggerConfig.ts @@ -0,0 +1,45 @@ +import { createLogger, format, transports } from "winston"; + +const gray = "\x1b[90m"; +const reset = "\x1b[0m"; +const white = "\x1b[97m"; +const red = "\x1b[31m"; +const green = "\x1b[32m"; +const yellow = "\x1b[33m"; +const blue = "\x1b[34m"; + +function colorLog(level: string, levelName: string) { + switch (level) { + case "info": + return `${green}${levelName}${reset}`; + case "debug": + return `${blue}${levelName}${reset}`; + case "error": + return `${red}${levelName}${reset}`; + case "warn": + return `${yellow}${levelName}${reset}`; + default: + return `${gray}UNKNOWN${reset}`; + } +} + +const logger = createLogger({ + level: "debug", + format: format.combine( + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf((info) => { + const level = info.level.toUpperCase().padEnd(5, " "); + const timestamp = `${gray}${info.timestamp}${reset}`; + const levelColorized = colorLog(info.level.toLowerCase(), level); + const message = `${white}${info.message}${reset}`; + + return `${timestamp} ${levelColorized} : ${message}`; + }), + ), + transports: [ + new transports.Console(), + new transports.File({ filename: "logs/app.log" }), + ], +}); + +export default logger; diff --git a/src/config/swaggerConfig.ts b/src/config/swaggerConfig.ts new file mode 100644 index 00000000..630805e9 --- /dev/null +++ b/src/config/swaggerConfig.ts @@ -0,0 +1,53 @@ +const options: { + definition: { + failOnErrors: boolean; + openapi: string; + info: { + title: string; + version: string; + description: string; + }; + components: { + securitySchemes: { + passwordAuth: { + type: string; + in: string; + name: string; + description: string; + }; + }; + }; + security: Array<{ + passwordAuth: any[]; + }>; + }; + apis: string[]; +} = { + definition: { + failOnErrors: true, + openapi: "3.0.0", + info: { + title: "DockStatAPI", + version: "2", + description: "An API used to query muliple docker hosts", + }, + components: { + securitySchemes: { + passwordAuth: { + type: "apiKey", + in: "header", + name: "x-password", + description: "Password required for authentication", + }, + }, + }, + security: [ + { + passwordAuth: [], + }, + ], + }, + apis: ["./src/routes/*/*.ts"], +}; + +export default options; diff --git a/controllers/containerController.js b/src/controllers/containerController.ts similarity index 58% rename from controllers/containerController.js rename to src/controllers/containerController.ts index f62ec5ce..1532681e 100644 --- a/controllers/containerController.js +++ b/src/controllers/containerController.ts @@ -1,27 +1,30 @@ -const fs = require("fs"); -const path = require("path"); -const { getDockerClient } = require("../utils/dockerClient"); -const logger = require("../utils/logger"); +import getDockerClient from "../utils/dockerClient"; +import logger from "../utils/logger"; +import { Request, Response } from "express"; -const getContainers = async (req, res) => { - const host = req.query.host || "local"; +const getContainers = async (req: Request, res: Response): Promise => { + const host: string = (req.query.host as string) || "local"; logger.info(`Fetching containers from host: ${host}`); try { const docker = getDockerClient(host); const containers = await docker.listContainers(); res.status(200).json(containers); - } catch (err) { + } catch (error: any) { logger.error( - `Error fetching containers from host: ${host} - ${err.message || "Unknown error"} - Full error: ${JSON.stringify(err, null, 2)}`, + `Error fetching containers from host: ${host} - ${error.message || "Unknown error"} - Full error: ${JSON.stringify(error, null, 2)}`, ); res.status(500).json({ - error: `Error fetching containers: ${err.message || "Unknown error"}`, + error: `Error fetching containers: ${error.message || "Unknown error"}`, }); } }; -const getContainerStats = async (containerID, containerHost) => { +const getContainerStats = async ( + containerID: string, + containerHost: string, + res: Response, +): Promise => { logger.info( `Fetching stats for container: ${containerID} from host: ${containerHost}`, ); @@ -33,7 +36,7 @@ const getContainerStats = async (containerID, containerHost) => { `Successfully fetched stats for container: ${containerID} from host: ${containerHost}`, ); res.status(200).json(stats); - } catch (error) { + } catch (error: any) { logger.error( `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${error.message}`, ); @@ -43,7 +46,7 @@ const getContainerStats = async (containerID, containerHost) => { } }; -module.exports = { +export default { getContainers, getContainerStats, }; diff --git a/controllers/databaseMigration.js b/src/controllers/databaseMigration.ts similarity index 55% rename from controllers/databaseMigration.js rename to src/controllers/databaseMigration.ts index 263de07f..45f88d12 100644 --- a/controllers/databaseMigration.js +++ b/src/controllers/databaseMigration.ts @@ -1,13 +1,13 @@ -const db = require("../config/db"); -const logger = require("../utils/logger"); +import db from "../config/db"; +import logger from "../utils/logger"; -function clearOldEntries() { - const twentyFourHoursAgo = Date.now() - 24 * 60 * 60 * 1000; +function clearOldEntries(): void { + const twentyFourHoursAgo: number = Date.now() - 24 * 60 * 60 * 1000; db.run( `DELETE FROM data WHERE createdAt < ?`, [twentyFourHoursAgo], - (err) => { + (err: Error | null) => { if (err) { logger.error("Error deleting old entries:", err.message); throw new Error("Database cleanup failed"); @@ -17,4 +17,4 @@ function clearOldEntries() { ); } -module.exports = clearOldEntries; +export default clearOldEntries; diff --git a/src/controllers/fetchData.ts b/src/controllers/fetchData.ts new file mode 100644 index 00000000..be9fdc7e --- /dev/null +++ b/src/controllers/fetchData.ts @@ -0,0 +1,80 @@ +import db from "../config/db"; +import fetchAllContainers from "../utils/containerService"; +import logger from "../utils/logger"; +import fs from "fs"; +const filePath = "./src/data/states.json"; + +let previousState: { [key: string]: string } = {}; + +interface Container { + name: string; + id: string; + state: string; + hostName: string; +} + +interface AllContainerData { + [host: string]: Container[] | { error: string }; +} + +const fetchData = async (): Promise => { + try { + const allContainerData: AllContainerData = + (await fetchAllContainers()) || {}; + + if (process.env.OFFLINE === "true") { + logger.info("No new data inserted --- OFFLINE MODE"); + } else { + db.run( + `INSERT INTO data (info) VALUES (?)`, + [JSON.stringify(allContainerData)], + function (error) { + if (error) { + logger.error("Error inserting data:", error); + return; + } + logger.info(`Data inserted with ID: ${this.lastID}`); + }, + ); + } + + const containerStatus: AllContainerData = {}; + + Object.keys(allContainerData).forEach((host) => { + const containers = allContainerData[host]; + + // Handle if the containers are an array, otherwise handle the error + if (Array.isArray(containers)) { + containerStatus[host] = containers.map((container: Container) => ({ + name: container.name, + id: container.id, + state: container.state, + hostName: container.hostName, + })); + } else { + // If there's an error, handle it separately + containerStatus[host] = { error: "Error fetching containers" }; + } + }); + + if (fs.existsSync(filePath)) { + const fileData = fs.readFileSync(filePath, "utf8"); + previousState = fileData ? JSON.parse(fileData) : {}; + } + + // Compare previous and current state + if (JSON.stringify(previousState) !== JSON.stringify(containerStatus)) { + fs.writeFileSync(filePath, JSON.stringify(containerStatus, null, 2)); + logger.info(`Container states saved to ${filePath}`); + // TODO: Add logic + notification levels per service + } else { + logger.info("No state change detected, notifications not triggered."); + } + } catch (error: any) { + logger.error( + `Error fetching data: ${JSON.stringify(error)} \nStack trace: ${error.stack}`, + ); + } +}; + +export default fetchData; diff --git a/controllers/frontendConfiguration.js b/src/controllers/frontendConfiguration.ts similarity index 73% rename from controllers/frontendConfiguration.js rename to src/controllers/frontendConfiguration.ts index cdbee131..6a5a6911 100644 --- a/controllers/frontendConfiguration.js +++ b/src/controllers/frontendConfiguration.ts @@ -1,18 +1,17 @@ -const fs = require("fs"); -const path = require("path"); -const dataPath = path.join(__dirname, "../data/frontendConfiguration.json"); -const logger = require("../utils/logger"); -const expression = +import fs from "fs"; +import logger from "../utils/logger"; +const dataPath: string = "./src/data/frontendConfiguration.json"; +const expression: string = "https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}([-a-zA-Z0-9()@:%_+.~#?&//=]*)"; const regex = new RegExp(expression); /////////////////////////////////////////////////////////////// // Hide Containers: -async function hideContainer(containerName) { +async function hideContainer(containerName: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -22,17 +21,17 @@ async function hideContainer(containerName) { data.push({ name: containerName, hidden: true }); await saveData(data); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } } -async function unhideContainer(containerName) { +async function unhideContainer(containerName: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -40,7 +39,7 @@ async function unhideContainer(containerName) { await saveData(data); cleanupData(); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -48,11 +47,11 @@ async function unhideContainer(containerName) { /////////////////////////////////////////////////////////////// // Tag containers -async function addTagToContainer(containerName, tag) { +async function addTagToContainer(containerName: string, tag: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -65,27 +64,27 @@ async function addTagToContainer(containerName, tag) { data.push({ name: containerName, tags: [tag] }); await saveData(data); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } } -async function removeTagFromContainer(containerName, tag) { +async function removeTagFromContainer(containerName: string, tag: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1 && data[containerIndex].tags) { data[containerIndex].tags = data[containerIndex].tags.filter( - (t) => t !== tag, + (t: any) => t !== tag, ); await saveData(data); cleanupData(); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -93,11 +92,11 @@ async function removeTagFromContainer(containerName, tag) { /////////////////////////////////////////////////////////////// // Pin containers -async function pinContainer(containerName) { +async function pinContainer(containerName: string) { try { - let data = await readData(); - const containerIndex = data.findIndex( - (container) => container.name === containerName, + let data: any = await readData(); + const containerIndex: number = data.findIndex( + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -107,17 +106,17 @@ async function pinContainer(containerName) { data.push({ name: containerName, pinned: true }); await saveData(data); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } } -async function unpinContainer(containerName) { +async function unpinContainer(containerName: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -125,7 +124,7 @@ async function unpinContainer(containerName) { await saveData(data); cleanupData(); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -133,12 +132,12 @@ async function unpinContainer(containerName) { /////////////////////////////////////////////////////////////// // Add/remove link from containers -async function setLink(containerName, link) { +async function setLink(containerName: string, link: string) { if (link.match(regex)) { try { - let data = await readData(); - const containerIndex = data.findIndex( - (container) => container.name === containerName, + let data: any = await readData(); + const containerIndex: any = data.findIndex( + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -148,7 +147,7 @@ async function setLink(containerName, link) { data.push({ name: containerName, link: `${link}` }); await saveData(data); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -158,11 +157,11 @@ async function setLink(containerName, link) { } } -async function removeLink(containerName) { +async function removeLink(containerName: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -170,7 +169,7 @@ async function removeLink(containerName) { await saveData(data); cleanupData(); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -178,11 +177,11 @@ async function removeLink(containerName) { /////////////////////////////////////////////////////////////// // Add/remove icon from containers -async function setIcon(containerName, icon, custom) { +async function setIcon(containerName: string, icon: string, custom: boolean) { try { let data = await readData(); - const containerIndex = data.findIndex( - (container) => container.name === containerName, + const containerIndex: number = data.findIndex( + (container: any) => container.name === containerName, ); if (custom === true) { @@ -202,17 +201,17 @@ async function setIcon(containerName, icon, custom) { await saveData(data); } } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } } -async function removeIcon(containerName) { +async function removeIcon(containerName: string) { try { let data = await readData(); const containerIndex = data.findIndex( - (container) => container.name === containerName, + (container: any) => container.name === containerName, ); if (containerIndex !== -1) { @@ -220,7 +219,7 @@ async function removeIcon(containerName) { await saveData(data); cleanupData(); } - } catch (error) { + } catch (error: any) { logger.error(error); throw new Error(error); } @@ -232,7 +231,7 @@ async function readData() { try { const data = await fs.promises.readFile(dataPath, "utf-8"); return JSON.parse(data); - } catch (error) { + } catch (error: any) { console.error("readData"); if (error.code === "ENOENT") { await saveData([]); @@ -243,7 +242,7 @@ async function readData() { } } -async function saveData(data) { +async function saveData(data: any) { try { await fs.promises.writeFile( dataPath, @@ -251,7 +250,7 @@ async function saveData(data) { "utf-8", ); logger.info("Succesfully wrote to file"); - } catch (error) { + } catch (error: any) { logger.error(error); } } @@ -276,12 +275,12 @@ async function cleanupData() { } await saveData(cleanedData); - } catch (error) { + } catch (error: any) { logger.error(error); } } -module.exports = { +export { hideContainer, unhideContainer, addTagToContainer, diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts new file mode 100644 index 00000000..e8557574 --- /dev/null +++ b/src/controllers/highAvailability.ts @@ -0,0 +1,274 @@ +import logger from "../utils/logger"; +import fs from "fs"; +import chokidar from "chokidar"; +import path from "path"; +import { promisify } from "util"; + +const sleep = promisify(setTimeout); + +interface HighAvailabilityConfig { + active: boolean; + master: boolean; + nodes: string[]; +} + +interface Node { + ip: string; + id: number; +} + +interface HaNodeConfig { + master: string; +} + +interface NodeCache { + [nodes: string]: Node; +} + +const haMasterPath: string = "./src/data/highAvailability.json"; +const haNodePath: string = "./src/data/haNode.json"; +const nodeCachePath: string = "./src/data/nodeCache.json"; +const useUnsafeConnection = process.env.HA_UNSAFE || "false"; +const lockFilePath: string = "./src/data/ha.lock"; + +const configFiles: string[] = [ + "./src/data/dockerConfig.json", + "./src/data/states.json", + "./src/data/template.json", + "./src/data/frontendConfiguration.json", + "./src/data/nodeCache.json", + "./src/data/usePassword.txt", + "./src/data/password.json", +]; + +async function acquireLock(): Promise { + while (fs.existsSync(lockFilePath)) { + logger.warn("Lock file exists, waiting..."); + await sleep(100); + } + + try { + await fs.promises.writeFile(lockFilePath, "locked", { flag: "wx" }); + logger.debug("Lock acquired."); + } catch (error) { + logger.error(`Error acquiring lock: ${(error as Error).message}`); + throw new Error("Failed to acquire lock."); + } +} + +async function releaseLock(): Promise { + try { + if (fs.existsSync(lockFilePath)) { + await fs.promises.unlink(lockFilePath); + logger.debug("Lock released."); + } + } catch (error) { + logger.error(`Error releasing lock: ${(error as Error).message}`); + } +} + +async function writeConfig( + data: HighAvailabilityConfig | NodeCache | HaNodeConfig, + filePath: string, +): Promise { + await acquireLock(); + try { + logger.debug(`Writing ${filePath}`); + const dirPath: string = path.dirname(filePath); + await fs.promises.mkdir(dirPath, { recursive: true }); + + const jsonData = JSON.stringify(data, null, 2); + await fs.promises.writeFile(filePath, jsonData); + + logger.debug(`${filePath} has been written.`); + } catch (error) { + logger.error(`Error writing config: ${(error as Error).message}`); + } finally { + await releaseLock(); + } +} + +async function readConfig(): Promise { + await acquireLock(); + try { + logger.debug("Reading HA-Config"); + const data: HighAvailabilityConfig = JSON.parse( + fs.readFileSync(haMasterPath, "utf-8"), + ); + return data; + } catch (error: any) { + logger.error(`Error reading HA-Config: ${(error as Error).message}`); + return null; + } finally { + await releaseLock(); + } +} + +async function prepareFilesForSync(): Promise> { + const fileData: Record = {}; + try { + for (const filePath of configFiles) { + const content = await fs.promises.readFile(filePath, "utf-8"); + fileData[filePath] = content; + } + } catch (error) { + logger.error(`Error preparing files for sync: ${(error as Error).message}`); + } + return fileData; +} + +async function checkApiReachable(node: string): Promise { + let nodeUrl = + useUnsafeConnection === "true" + ? `http://${node}/api/status` + : `https://${node}/api/status`; + + try { + const response = await fetch(nodeUrl); + if (!response.ok) { + logger.error(`Failed to reach node ${node}. Status: ${response.status}`); + return false; + } + + const data = await response.json(); + if (data.ApiReachable) { + logger.info(`Node ${node} is reachable.`); + return true; + } else { + logger.error(`Node ${node} is not reachable. ApiReachable: false`); + return false; + } + } catch (error) { + logger.error(`Error reaching node ${node}: ${(error as Error).message}`); + return false; + } +} + +async function synchronizeFilesWithNodes(): Promise { + const haConfig = await readConfig(); + + if (!haConfig || !haConfig.master || haConfig.nodes.length === 0) { + logger.warn("No slave nodes to synchronize with."); + return; + } + + const files = await prepareFilesForSync(); + + for (const node of haConfig.nodes) { + if (!(await checkApiReachable(node))) { + logger.warn( + `Skipping file sync with ${node} due to connectivity issues.`, + ); + continue; // Skip synchronization if the node is unreachable + } + + let nodeUrl = + useUnsafeConnection == "true" + ? `http://${node}/ha/sync` + : `https://${node}/ha/sync`; + + logger.info(`Synchronizing files with node: ${node}`); + + const response = await fetch(nodeUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ files }), + }); + + if (response.ok) { + logger.info(`Files synchronized successfully with node: ${node}`); + } else { + logger.error( + `Failed to synchronize files with node ${node}. Status: ${response.status}`, + ); + } + } +} + +function monitorConfigFiles(): void { + const watcher = chokidar.watch(configFiles, { persistent: true }); + + watcher + .on("change", async (filePath) => { + logger.info(`File changed: ${filePath}. Initiating synchronization.`); + await synchronizeFilesWithNodes(); + }) + .on("error", (error) => { + logger.error(`Error watching files: ${(error as Error).message}`); + }); + + logger.info("Started monitoring configuration files for changes."); +} + +async function startMasterNode() { + if (process.env.HA_MASTER == "true") { + if (!process.env.HA_MASTER_IP) { + logger.error( + "Master's IP is not set, please set the HA_MASTER_IP variable (example: 10.0.0.4:9876)", + ); + } else { + const haNodeConfig: HaNodeConfig = { + master: "HA_MASTER_IP", + }; + const haConfig: HighAvailabilityConfig = { + active: true, + master: true, + nodes: process.env.HA_NODE + ? process.env.HA_NODE.split(",").map((node) => node.trim()) + : [], + }; + + const nodeCache: NodeCache = process.env.HA_NODE + ? process.env.HA_NODE.split(",").reduce((cache, node, index) => { + const [ip, id] = node.trim().split(":"); + if (ip && id) { + cache[`node${index + 1}`] = { ip, id: parseInt(id, 10) }; + } + return cache; + }, {} as NodeCache) + : {}; + + logger.debug("Writing HA-Config(s)"); + await writeConfig(haConfig, haMasterPath); + await writeConfig(haNodeConfig, haNodePath); + await writeConfig(nodeCache, nodeCachePath); + + logger.info("Running startup sync..."); + await synchronizeFilesWithNodes(); + logger.info("Watching config files in ./data"); + monitorConfigFiles(); + } + } else { + logger.info("This is a slave node"); + } +} + +async function ensureFileExists( + filePath: string, + content: string, +): Promise { + await acquireLock(); + try { + const dirPath = path.dirname(filePath); + await fs.promises.mkdir(dirPath, { recursive: true }); + await fs.promises.writeFile(filePath, content, { flag: "w" }); + logger.info(`File created/updated: ${filePath}`); + } catch (error) { + logger.error( + `Error creating/updating file ${filePath}: ${(error as Error).message}`, + ); + } finally { + await releaseLock(); + } +} + +export { + HighAvailabilityConfig, + writeConfig, + readConfig, + prepareFilesForSync, + synchronizeFilesWithNodes, + monitorConfigFiles, + startMasterNode, + ensureFileExists, +}; diff --git a/src/controllers/notificationController.ts b/src/controllers/notificationController.ts new file mode 100644 index 00000000..e34eecda --- /dev/null +++ b/src/controllers/notificationController.ts @@ -0,0 +1,62 @@ +import notify from "../utils/notifications/_notify"; +import logger from "../utils/logger"; + +const notificationTypes = { + discord: !!process.env.DISCORD_WEBHOOK_URL, + email: !!( + process.env.EMAIL_SENDER && + process.env.EMAIL_RECIPIENT && + process.env.EMAIL_PASSWORD + ), + pushbullet: !!process.env.PUSHBULLET_ACCESS_TOKEN, + pushover: !!(process.env.PUSHOVER_API_TOKEN && process.env.PUSHOVER_USER_KEY), + slack: !!process.env.SLACK_WEBHOOK_UR, + telegram: !!(process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID), + whatsapp: !!(process.env.WHATSAPP_API_URL && process.env.WHATSAPP_RECIPIENT), + custom: !!process.env.CUSTOM_NOTIFICATION, + customList: process.env.CUSTOM_NOTIFICATION, +}; + +async function sendNotification(containerId: string) { + if (notificationTypes.discord) { + logger.debug(`Sending notification via discord (${containerId})`); + notify("discord", containerId); + } + if (notificationTypes.email) { + logger.debug(`Sending notification via E-Mail (${containerId})`); + notify("email", containerId); + } + if (notificationTypes.pushbullet) { + logger.debug(`Sending notification via Pushbullet (${containerId})`); + notify("pushbullet", containerId); + } + if (notificationTypes.pushover) { + logger.debug(`Sending notification via Pushover (${containerId})`); + notify("pushover", containerId); + } + if (notificationTypes.slack) { + logger.debug(`Sending notification via Slack (${containerId})`); + notify("slack", containerId); + } + if (notificationTypes.telegram) { + logger.debug(`Sending notification via Telegram (${containerId})`); + notify("slack", containerId); + } + if (notificationTypes.whatsapp) { + logger.debug(`Sending notification via Pushbullet (${containerId})`); + notify("whatsapp", containerId); + } + if (notificationTypes.custom) { + const elements: undefined | string[] = notificationTypes.customList + ? notificationTypes.customList.split(",") + : undefined; + if (elements) { + elements.forEach((element) => { + logger.debug(`Sending custom notification ${element} (${containerId})`); + notify(`custom/${element}`, containerId); + }); + } else { + logger.error("Error getting custom notifications"); + } + } +} diff --git a/src/controllers/proxy.ts b/src/controllers/proxy.ts new file mode 100644 index 00000000..681adef7 --- /dev/null +++ b/src/controllers/proxy.ts @@ -0,0 +1,14 @@ +import { Application } from "express"; +import logger from "../utils/logger"; + +export default function trustedProxies(app: Application) { + const trusted: string = process.env.TRUSTED_PROXYS || ""; + + if (!trusted) { + logger.warn( + "No trusted Proxy configured, if ran behind a proxy please configure it according to the docs", + ); + } else { + app.set("trust proxy", trusted); + } +} diff --git a/controllers/scheduler.js b/src/controllers/scheduler.ts similarity index 56% rename from controllers/scheduler.js rename to src/controllers/scheduler.ts index e19b17eb..763b67f9 100644 --- a/controllers/scheduler.js +++ b/src/controllers/scheduler.ts @@ -1,15 +1,19 @@ -const fetchData = require("./fetchData"); -const logger = require("../utils/logger"); -const db = require("../config/db"); +import fetchData from "./fetchData"; +import logger from "../utils/logger"; +import db from "../config/db"; const regex = /(\d{1,5})([smh])/g; let fetchInterval = 5 * 60 * 1000; // Fetch data every 5 minutes by default -let intervalId; +const cleanupInterval = 24 * 60 * 60 * 1000; // every 24hrs +let intervalId: NodeJS.Timeout; const scheduleFetch = () => { - fetchData().then(() => { + try { + fetchData(); cleanupOldEntries(); - }); + } catch (error: any) { + logger.error(`Error during scheduled fetch: ${error}`); + } intervalId = setInterval(() => { logger.info( @@ -18,18 +22,24 @@ const scheduleFetch = () => { fetchData(); }, fetchInterval); - cleanupIntervalId = setInterval( - () => { - cleanupOldEntries(); - }, - 24 * 60 * 60 * 1000, - ); + setInterval(() => { + cleanupOldEntries(); + }, cleanupInterval); logger.info(`Data fetching scheduled every ${fetchInterval / 1000} seconds.`); logger.info("Old entries cleanup scheduled every 24 hours."); + + // Additional 20-second interval to log process exit listeners, if any + setInterval(() => { + const exitListeners = process.listeners("exit"); + + if (exitListeners.length > 0) { + logger.info(`Exit listeners detected: ${exitListeners}`); + } + }, 20000); }; -const setFetchInterval = (newInterval) => { +const setFetchInterval = (newInterval: number) => { if (intervalId) { clearInterval(intervalId); logger.info("Cleared existing fetch interval."); @@ -39,8 +49,8 @@ const setFetchInterval = (newInterval) => { logger.info(`Fetch interval updated to ${fetchInterval / 1000} seconds.`); }; -const parseInterval = (interval) => { - const timeUnits = { +const parseInterval = (interval: string) => { + const timeUnits: { [key: string]: number } = { s: 1000, m: 60 * 1000, h: 60 * 60 * 1000, @@ -69,16 +79,11 @@ const cleanupOldEntries = async () => { Date.now() - 24 * 60 * 60 * 1000, ).toISOString(); try { - await db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo); + db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo, Error); logger.info("Old entries cleared from the database."); - } catch (error) { - logger.error(`Error clearing old entries: ${error.message}`); + } catch (Error: any) { + logger.error(`Error clearing old entries: ${Error.message}`); } }; -module.exports = { - scheduleFetch, - setFetchInterval, - parseInterval, - getCurrentSchedule, -}; +export { scheduleFetch, setFetchInterval, parseInterval, getCurrentSchedule }; diff --git a/middleware/usePassword.txt b/src/data/usePassword.txt similarity index 100% rename from middleware/usePassword.txt rename to src/data/usePassword.txt diff --git a/src/init.ts b/src/init.ts new file mode 100644 index 00000000..3979eb6f --- /dev/null +++ b/src/init.ts @@ -0,0 +1,47 @@ +import express, { Request, Response, NextFunction } from "express"; +import swaggerDocs from "./utils/swaggerDocs"; +import auth from "./routes/auth/routes"; +import data from "./routes/data/routes"; +import frontend from "./routes/frontendController/routes"; +import api from "./routes/getter/routes"; +import notificationService from "./routes/notifications/routes"; +import conf from "./routes/setter/routes"; +import authMiddleware from "./middleware/authMiddleware"; +import ha from "./routes/highavailability/routes"; +import trustedProxies from "./controllers/proxy"; +import { limiter } from "./middleware/rateLimiter"; +import { scheduleFetch } from "./controllers/scheduler"; +import cors from "cors"; +import { blockWhileLocked } from "./middleware/checkLock"; + +const initializeApp = (app: express.Application): void => { + app.use(cors()); + app.use(express.json()); + app.use("/api-docs", (req: Request, res: Response, next: NextFunction) => + next(), + ); + + swaggerDocs(app as any); + trustedProxies(app); // Configures proxies using CSV string + scheduleFetch(); + + app.use("/api", limiter, authMiddleware, blockWhileLocked, api); + app.use("/conf", limiter, authMiddleware, blockWhileLocked, conf); + app.use("/auth", limiter, authMiddleware, blockWhileLocked, auth); + app.use("/data", limiter, authMiddleware, blockWhileLocked, data); + app.use("/frontend", limiter, authMiddleware, blockWhileLocked, frontend); + app.use( + "/notification-service", + limiter, + authMiddleware, + blockWhileLocked, + notificationService, + ); + app.use("/ha", limiter, authMiddleware, ha); + + app.get("/", (req: Request, res: Response) => { + res.redirect("/api-docs"); + }); +}; + +export default initializeApp; diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts new file mode 100644 index 00000000..8caad081 --- /dev/null +++ b/src/middleware/authMiddleware.ts @@ -0,0 +1,52 @@ +import bcrypt from "bcrypt"; +import fs from "fs"; +import { Request, Response, NextFunction } from "express"; +import logger from "../utils/logger"; + +const passwordFile = "./src/data/password.json"; +const passwordBool = "./src/data/usePassword.txt"; + +async function authMiddleware( + req: Request, + res: Response, + next: NextFunction, +): Promise { + try { + const authStatusData = await fs.promises.readFile(passwordBool, "utf8"); + const isAuthEnabled = authStatusData.trim() === "true"; + + if (!isAuthEnabled) { + logger.warn("You are not using authentication, please enable it."); + logger.debug("Authentication disabled, skipping login process..."); + return next(); + } + + const providedPassword = req.headers["x-password"]; + if (!providedPassword) { + logger.error("Password required - Denied"); + res.status(401).json({ message: "Password required" }); + return; + } + + const passwordData = await fs.promises.readFile(passwordFile, "utf8"); + const storedData = JSON.parse(passwordData); + + const passwordMatch = await bcrypt.compare( + providedPassword as string, + storedData.hash, + ); + if (!passwordMatch) { + logger.error("Invalid Password - Denied access"); + res.status(401).json({ message: "Invalid password" }); + return; + } + + logger.debug("Authentication succesfull"); + next(); + } catch (error: any) { + logger.error("Error in authMiddleware:", error); + res.status(500).json({ message: "Internal server error" }); + } +} + +export default authMiddleware; diff --git a/src/middleware/checkLock.ts b/src/middleware/checkLock.ts new file mode 100644 index 00000000..747889dc --- /dev/null +++ b/src/middleware/checkLock.ts @@ -0,0 +1,19 @@ +import fs from "fs"; +import { Request, Response, NextFunction } from "express"; + +const lockFilePath = "./src/data/ha.lock"; + +export function blockWhileLocked( + req: Request, + res: Response, + next: NextFunction, +): void { + if (fs.existsSync(lockFilePath)) { + res.status(503).json({ + error: + "Service unavailable. The high-availability lock is currently active. Please try again later.", + }); + } else { + next(); + } +} diff --git a/middleware/rateLimiter.js b/src/middleware/rateLimiter.ts similarity index 100% rename from middleware/rateLimiter.js rename to src/middleware/rateLimiter.ts diff --git a/src/misc/createEnvFile.sh b/src/misc/createEnvFile.sh new file mode 100644 index 00000000..cbd8244d --- /dev/null +++ b/src/misc/createEnvFile.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +# Version +VERSION="$1" + +# Docker +if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then + RUNNING_IN_DOCKER="true" +else + RUNNING_IN_DOCKER="false" +fi +echo " +{ + \"RUNNING_IN_DOCKER\": \"${RUNNING_IN_DOCKER}\", + \"HA_MASTER\": \"${HA_MASTER}\", + \"HA_MASTER_IP\": \"${HA_MASTER_IP}\", + \"HA_NODE\": \"${HA_NODE}\", + \"HA_UNSAFE\": \"${HA_UNSAFE}\", + \"DISCORD_WEBHOOK_URL\": \"${DISCORD_WEBHOOK_URL}\", + \"EMAIL_SENDER\": \"${EMAIL_SENDER}\", + \"EMAIL_RECIPIENT\": \"${EMAIL_RECIPIENT}\", + \"EMAIL_PASSWORD\": \"${EMAIL_PASSWORD}\", + \"EMAIL_SERVICE\": \"${EMAIL_SERVICE}\", + \"PUSHBULLET_ACCESS_TOKEN\": \"${PUSHBULLET_ACCESS_TOKEN}\", + \"PUSHOVER_USER_KEY\": \"${PUSHOVER_USER_KEY}\", + \"PUSHOVER_API_TOKEN\": \"${PUSHOVER_API_TOKEN}\", + \"SLACK_WEBHOOK_URL\": \"${SLACK_WEBHOOK_URL}\", + \"TELEGRAM_BOT_TOKEN\": \"${TELEGRAM_BOT_TOKEN}\", + \"TELEGRAM_CHAT_ID\": \"${TELEGRAM_CHAT_ID}\", + \"WHATSAPP_API_URL\": \"${WHATSAPP_API_URL}\", + \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\", + \"CUSTOM_NOTIFICATION\": \"${CUSTOM_NOTIFICATION}\" +} +" > /api/src/data/variables.conf diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt new file mode 100644 index 00000000..7e77f2c9 --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -0,0 +1,106 @@ +flowchart LR + +0["server.ts"] +subgraph 1["controllers"] +2["highAvailability.ts"] +3["scheduler.ts"] +6["fetchData.ts"] +K["frontendConfiguration.ts"] +end +subgraph 4["config"] +5["db.ts"] +19["swaggerConfig.ts"] +end +subgraph 7["utils"] +8["containerService.ts"] +9["dockerClient.ts"] +N["connectionChecker.ts"] +P["extractHostData.ts"] +Q["writeOfflineLog.ts"] +subgraph V["notifications"] +W["_notify.ts"] +X["discord.ts"] +Y["_template.ts"] +Z["email.ts"] +10["pushbullet.ts"] +11["pushover.ts"] +12["slack.ts"] +13["telegram.ts"] +14["whatsapp.ts"] +end +end +subgraph A["middleware"] +B["authMiddleware.ts"] +C["rateLimiter.ts"] +end +subgraph D["routes"] +subgraph E["auth"] +F["routes.ts"] +end +subgraph G["data"] +H["routes.ts"] +end +subgraph I["frontendController"] +J["routes.ts"] +end +subgraph L["getter"] +M["routes.ts"] +end +subgraph R["highavailability"] +S["routes.ts"] +end +subgraph T["notifications"] +U["routes.ts"] +end +subgraph 15["setter"] +16["routes.ts"] +end +end +O["net"] +subgraph 17["swagger"] +18["swaggerDocs.ts"] +end +0-->2 +0-->3 +0-->B +0-->C +0-->F +0-->H +0-->J +0-->M +0-->S +0-->U +0-->16 +0-->18 +3-->5 +3-->6 +6-->5 +6-->8 +8-->9 +H-->5 +J-->K +M-->3 +M-->N +M-->8 +M-->9 +M-->P +M-->Q +N-->O +S-->2 +U-->W +W-->X +W-->Z +W-->10 +W-->11 +W-->12 +W-->13 +W-->14 +X-->Y +Z-->Y +10-->Y +11-->Y +12-->Y +13-->Y +14-->Y +16-->3 +18-->19 diff --git a/src/misc/dependencyGraphs/mermaid-api.txt b/src/misc/dependencyGraphs/mermaid-api.txt new file mode 100644 index 00000000..e7c85cc8 --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-api.txt @@ -0,0 +1,32 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["getter"] +2["routes.ts"] +end +end +subgraph 3["controllers"] +4["scheduler.ts"] +7["fetchData.ts"] +end +subgraph 5["config"] +6["db.ts"] +end +subgraph 8["utils"] +9["containerService.ts"] +A["dockerClient.ts"] +B["connectionChecker.ts"] +C["extractHostData.ts"] +D["writeOfflineLog.ts"] +end +2-->4 +2-->B +2-->9 +2-->A +2-->C +2-->D +4-->6 +4-->7 +7-->6 +7-->9 +9-->A diff --git a/misc/dependencyGraphs/mermaid-auth.txt b/src/misc/dependencyGraphs/mermaid-auth.txt similarity index 80% rename from misc/dependencyGraphs/mermaid-auth.txt rename to src/misc/dependencyGraphs/mermaid-auth.txt index e7ab0669..aaeb683b 100644 --- a/misc/dependencyGraphs/mermaid-auth.txt +++ b/src/misc/dependencyGraphs/mermaid-auth.txt @@ -2,7 +2,7 @@ flowchart LR subgraph 0["routes"] subgraph 1["auth"] -2["routes.js"] +2["routes.ts"] end end diff --git a/src/misc/dependencyGraphs/mermaid-conf.txt b/src/misc/dependencyGraphs/mermaid-conf.txt new file mode 100644 index 00000000..ba9ca669 --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-conf.txt @@ -0,0 +1,24 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["setter"] +2["routes.ts"] +end +end +subgraph 3["controllers"] +4["scheduler.ts"] +7["fetchData.ts"] +end +subgraph 5["config"] +6["db.ts"] +end +subgraph 8["utils"] +9["containerService.ts"] +A["dockerClient.ts"] +end +2-->4 +4-->6 +4-->7 +7-->6 +7-->9 +9-->A diff --git a/misc/dependencyGraphs/mermaid-data.txt b/src/misc/dependencyGraphs/mermaid-data.txt similarity index 78% rename from misc/dependencyGraphs/mermaid-data.txt rename to src/misc/dependencyGraphs/mermaid-data.txt index e212edcb..107d46af 100644 --- a/misc/dependencyGraphs/mermaid-data.txt +++ b/src/misc/dependencyGraphs/mermaid-data.txt @@ -2,10 +2,10 @@ flowchart LR subgraph 0["routes"] subgraph 1["data"] -2["routes.js"] +2["routes.ts"] end end subgraph 3["config"] -4["db.js"] +4["db.ts"] end 2-->4 diff --git a/misc/dependencyGraphs/mermaid-frontend.txt b/src/misc/dependencyGraphs/mermaid-frontend.txt similarity index 71% rename from misc/dependencyGraphs/mermaid-frontend.txt rename to src/misc/dependencyGraphs/mermaid-frontend.txt index 35b4e61b..03340053 100644 --- a/misc/dependencyGraphs/mermaid-frontend.txt +++ b/src/misc/dependencyGraphs/mermaid-frontend.txt @@ -2,10 +2,10 @@ flowchart LR subgraph 0["routes"] subgraph 1["frontendController"] -2["routes.js"] +2["routes.ts"] end end subgraph 3["controllers"] -4["frontendConfiguration.js"] +4["frontendConfiguration.ts"] end 2-->4 diff --git a/src/misc/dependencyGraphs/mermaid-ha.txt b/src/misc/dependencyGraphs/mermaid-ha.txt new file mode 100644 index 00000000..ce156053 --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-ha.txt @@ -0,0 +1,11 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["highavailability"] +2["routes.ts"] +end +end +subgraph 3["controllers"] +4["highAvailability.ts"] +end +2-->4 diff --git a/src/misc/dependencyGraphs/mermaid-notificationService.txt b/src/misc/dependencyGraphs/mermaid-notificationService.txt new file mode 100644 index 00000000..cef6c2cd --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-notificationService.txt @@ -0,0 +1,35 @@ +flowchart LR + +subgraph 0["routes"] +subgraph 1["notifications"] +2["routes.ts"] +end +end +subgraph 3["utils"] +subgraph 4["notifications"] +5["_notify.ts"] +6["discord.ts"] +7["_template.ts"] +8["email.ts"] +9["pushbullet.ts"] +A["pushover.ts"] +B["slack.ts"] +C["telegram.ts"] +D["whatsapp.ts"] +end +end +2-->5 +5-->6 +5-->8 +5-->9 +5-->A +5-->B +5-->C +5-->D +6-->7 +8-->7 +9-->7 +A-->7 +B-->7 +C-->7 +D-->7 diff --git a/src/misc/entrypoint.sh b/src/misc/entrypoint.sh new file mode 100755 index 00000000..ff5cc617 --- /dev/null +++ b/src/misc/entrypoint.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +VERSION="2.0.0" + +echo -e " +\033[1;32mWelcome to\033[0m + +\033[1;34m###### ###### #### ### ### #### ######### ###### #########\033[0m +\033[1;34m### ### ### ### ### ### ### ### ### ### ### ###\033[0m +\033[1;34m### ### ### ### ### ###### #### ### ### ### ###\033[0m +\033[1;34m### ### ### ### ### ### ### #### ### ############ ###\033[0m +\033[1;34m### ### ### ### ### ### ### #### ### ### ### ###\033[0m +\033[1;34m###### ###### #### ### ### #### ### ### ### ### \033[0m(\033[1;33mAPI - v${VERSION}\033[0m) + +\033[1;36mUseful links:\033[0m + +- Documentation: \033[1;32mhttps://outline.itsnik.de/s/dockstat\033[0m +- GitHub (Frontend): \033[1;32mhttps://github.com/its4nik/dockstat\033[0m +- GitHub (Backend): \033[1;32mhttps://github.com/its4nik/dockstatapi\033[0m +- API Documentation: \033[1;32mhttp://localhost:7000/api-docs\033[0m + +\033[1;35mSummary:\033[0m + +DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. + +" + +bash "./createEnvFile.sh" "$VERSION" + +exec node src/server.js diff --git a/src/misc/minifyDist.sh b/src/misc/minifyDist.sh new file mode 100644 index 00000000..0c256170 --- /dev/null +++ b/src/misc/minifyDist.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +dist="$(pwd)/dist" + +run_script() { + npx uglifyjs --no-annotations --in-situ "$1" > /dev/null + echo "✔️ Minified : $(basename "$1")" +} + +if [ -d "$dist" ]; then + echo "::: Dist directory exists." +else + echo "::: Dist does not exist... Running npx tsc" + npx tsc +fi + +max_jobs=$(nproc) +job_count=0 + +for file in $(find "$dist" -type f); do + run_script "$file" & + ((job_count++)) + + if ((job_count >= max_jobs)); then + wait + job_count=0 + fi +done + +wait + +echo + +if [[ $1 == "--build-only" ]]; then + exit 0 +fi + +node dist/server.js diff --git a/src/routes/auth/routes.ts b/src/routes/auth/routes.ts new file mode 100644 index 00000000..4af13884 --- /dev/null +++ b/src/routes/auth/routes.ts @@ -0,0 +1,174 @@ +import { Router, Request, Response } from "express"; +import bcrypt from "bcrypt"; +import fs from "fs/promises"; +import logger from "../../utils/logger"; +const passwordFile: string = "./src/data/password.json"; +const passwordBool: string = "./src/data/usePassword.txt"; +const saltRounds: number = 10; +const router: Router = Router(); + +let passwordData: { + hash: string; + salt: string; +}; + +async function authEnabled(): Promise { + let isAuthEnabled: boolean = false; + let data: string = ""; + try { + data = await fs.readFile(passwordBool, "utf8"); + isAuthEnabled = data.trim() === "true"; + return isAuthEnabled; + } catch (error: any) { + logger.error("Error reading file: ", error); + return isAuthEnabled; + } +} + +async function readPasswordFile() { + let data: string = ""; + try { + data = await fs.readFile(passwordFile, "utf8"); + return data; + } catch (error: any) { + logger.error("Could not read saved password: ", error); + return data; + } +} + +async function writePasswordFile(passwordData: string) { + try { + await fs.writeFile(passwordFile, passwordData); + setTrue(); + logger.debug("Authentication enabled"); + return "Authentication enabled"; + } catch (error: any) { + logger.error("Error writing password file:", error); + return error; + } +} + +async function setTrue() { + try { + await fs.writeFile(passwordBool, "true", "utf8"); + logger.info(`Enabled authentication`); + return; + } catch (error: any) { + logger.error("Error writing to the file:", error); + return; + } +} + +async function setFalse() { + try { + await fs.writeFile(passwordBool, "false", "utf8"); + logger.info(`Disabled authentication`); + return; + } catch (error: any) { + logger.error("Error writing to the file:", error); + return; + } +} + +/** + * @swagger + * /auth/enable: + * post: + * summary: Enable authentication by setting a password + * tags: [Authentication] + * parameters: + * - name: password + * in: query + * required: true + * responses: + * 200: + * description: Authentication enabled. + * 400: + * description: Password is required. + * 500: + * description: Error saving password. + */ +router.post("/enable", async (req: Request, res: Response): Promise => { + try { + const password = req.query.password as string; + + if (await authEnabled()) { + logger.error( + "Password Authentication is already enabled, please deactivate it first", + ); + res.status(401).json({ + message: + "Password Authentication is already enabled, please deactivate it first", + }); + return; + } + + if (!password) { + logger.error("Password is required"); + res.status(400).json({ message: "Password is required" }); + return; + } + + const salt = await bcrypt.genSalt(saltRounds); + const hash = await bcrypt.hash(password, salt); + + const passwordData = { hash, salt }; + writePasswordFile(JSON.stringify(passwordData)); + + res + .status(200) + .json({ message: "Password Authentication enabled successfully" }); + } catch (error) { + logger.error(`Error enabling password authentication: ${error}`); + res.status(500).json({ message: "An error occurred" }); + } +}); + +/** + * @swagger + * /auth/disable: + * post: + * summary: Disable authentication by providing the existing password + * tags: [Authentication] + * parameters: + * - name: password + * in: query + * required: true + * responses: + * 200: + * description: Authentication disabled. + * 400: + * description: Password is required. + * 401: + * description: Invalid password. + * 500: + * description: Error disabling authentication. + */ +router.post("/disable", async (req: Request, res: Response): Promise => { + try { + const password = req.query.password as string; + + if (!password) { + logger.error("Password is required!"); + res.status(400).json({ message: "Password is required" }); + return; + } + + const storedData = JSON.parse(await readPasswordFile()); + + const isPasswordValid = await bcrypt.compare(password, storedData.hash); + if (!isPasswordValid) { + logger.error("Invalid password"); + res.status(401).json({ message: "Invalid password" }); + return; + } + + await setFalse(); // Assuming this is an async function + res.status(200).json({ message: "Authentication disabled" }); + } catch (error) { + logger.error(`Error disabling authentication: ${error}`); + res.status(500).json({ message: "An error occurred" }); + } +}); + +export default router; diff --git a/src/routes/data/routes.ts b/src/routes/data/routes.ts new file mode 100644 index 00000000..0e9a6e36 --- /dev/null +++ b/src/routes/data/routes.ts @@ -0,0 +1,201 @@ +import express from "express"; +const router = express.Router(); +import db from "../../config/db"; +import logger from "../../utils/logger"; + +interface DataRow { + info: string; +} + +function formatRows(rows: DataRow[]): Record { + return rows.reduce( + (acc: Record, row, index: number): Record => { + acc[index] = JSON.parse(row.info); + return acc; + }, + {}, + ); +} + +/** + * @swagger + * /data/latest: + * get: + * summary: Retrieve the latest container statistics for a specific host + * tags: [Database queries] + * responses: + * 200: + * description: A JSON object containing the latest container statistics for the specified host. + * content: + * application/json: + * schema: + * type: object + * properties: + * Fin-2: + * type: array + * items: + * type: object + * properties: + * name: + * type: string + * description: The name of the container + * example: "Container A" + * id: + * type: string + * description: Unique identifier for the container + * example: "abcd1234" + * hostName: + * type: string + * description: Name of the host system running this container + * example: "Fin-2" + * state: + * type: string + * description: Current state of the container + * example: "running" + * cpu_usage: + * type: number + * description: CPU usage percentage for this container + * example: 30 + * mem_usage: + * type: number + * description: Memory usage in bytes + * example: 2097152 + * mem_limit: + * type: number + * description: Memory limit in bytes set for this container + * example: 8123764736 + * net_rx: + * type: number + * description: Total network received bytes since container start + * example: 151763111 + * net_tx: + * type: number + * description: Total network transmitted bytes since container start + * example: 7104386 + * current_net_rx: + * type: number + * description: Current received bytes in the recent period + * example: 1048576 + * current_net_tx: + * type: number + * description: Current transmitted bytes in the recent period + * example: 524288 + * networkMode: + * type: string + * description: Networking mode for the container + * example: "bridge" + */ +router.get("/latest", (req, res) => { + db.get( + "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", + (error, row: any) => { + if (error) { + logger.error("Error fetching latest data:", error.message); + return res.status(500).json({ error: "Internal server error" }); + } + + if (!row) { + logger.warn("No data available for /data/latest"); + return res.status(404).json({ error: "No data available" }); + } + + logger.debug("Fetching /data/latest"); + res.json(JSON.parse(row.info)); + }, + ); +}); + +/** + * @swagger + * /data/time/24h: + * get: + * summary: Retrieve container statistics entries from the last 24 hours + * tags: [Database queries] + * responses: + * 200: + * description: A numbered array of 'info' JSON objects from the last 24 hours. + * content: + * application/json: + * schema: + * type: object + * properties: + * 0: + * type: object + * description: Statistics for the first entry within 24 hours. + * properties: + * name: + * type: string + * example: "Container A" + * id: + * type: string + * example: "abcd1234" + * cpu_usage: + * type: number + * example: 30 + * mem_usage: + * type: number + * example: 2048 + * 1: + * type: object + * description: Statistics for the second entry within 24 hours. + * properties: + * name: + * type: string + * example: "Container B" + * id: + * type: string + * example: "efgh5678" + * cpu_usage: + * type: number + * example: 45 + * mem_usage: + * type: number + * example: 3072 + */ +router.get("/time/24h", (req, res) => { + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + db.all( + "SELECT info FROM data WHERE timestamp >= ?", + [oneDayAgo], + (error, rows: DataRow[]) => { + if (error) { + logger.error("Error fetching data from last 24 hours:", error.message); + return res.status(500).json({ error: "Internal server error" }); + } + logger.debug("Fetching /data/time/24h"); + res.json(formatRows(rows)); + }, + ); +}); + +/** + * @swagger + * /data/clear: + * delete: + * summary: Clear all container statistics entries from the database + * tags: [Database queries] + * responses: + * 200: + * description: A message indicating whether the database was cleared successfully. + * content: + * application/json: + * schema: + * type: object + * properties: + * message: + * type: string + * description: Success message upon database clearance + * example: "Database cleared successfully." + */ +router.delete("/clear", (req, res) => { + db.run("DELETE FROM data", (err) => { + if (err) { + logger.error("Error clearing the database:", err.message); + return res.status(500).json({ error: "Internal server error" }); + } + logger.debug("Database cleared successfully"); + res.json({ message: "Database cleared successfully" }); + }); +}); + +export default router; diff --git a/routes/frontendController/routes.js b/src/routes/frontendController/routes.ts similarity index 97% rename from routes/frontendController/routes.js rename to src/routes/frontendController/routes.ts index de08c7a5..fe5d8411 100644 --- a/routes/frontendController/routes.js +++ b/src/routes/frontendController/routes.ts @@ -1,6 +1,7 @@ -const express = require("express"); +import express from "express"; +import logger from "../../utils/logger"; const router = express.Router(); -const { +import { hideContainer, unhideContainer, addTagToContainer, @@ -11,7 +12,7 @@ const { removeLink, setIcon, removeIcon, -} = require("../../controllers/frontendConfiguration"); +} from "../../controllers/frontendConfiguration"; /* ____ ___ ____ _____ @@ -67,9 +68,9 @@ router.post("/show/:containerName", async (req, res) => { const { containerName } = req.params; try { await unhideContainer(containerName); - res.json({ success: true, message: "Container unhidden successfully." }); - } catch (error) { - res.status(500).json({ success: false, error: error.message }); + res.status(200).json({ message: "Container unhidden successfully." }); + } catch (error: any) { + res.status(500).json({ error: error.message }); } }); @@ -126,7 +127,7 @@ router.post("/tag/:containerName/:tag", async (req, res) => { try { await addTagToContainer(containerName, tag); res.json({ success: true, message: "Tag added successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -178,7 +179,7 @@ router.post("/pin/:containerName", async (req, res) => { try { await pinContainer(containerName); res.json({ success: true, message: "Container pinned successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -236,7 +237,7 @@ router.post("/add-link/:containerName/:link", async (req, res) => { try { await setLink(containerName, link); res.json({ success: true, message: "Link added successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -304,7 +305,7 @@ router.post( await setIcon(containerName, icon, custom); res.json({ success: true, message: "Icon added successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }, @@ -366,7 +367,7 @@ router.delete("/hide/:containerName", async (req, res) => { try { await hideContainer(target); res.json({ success: true, message: `Container, ${target}, hidden.` }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -424,7 +425,7 @@ router.delete("/remove-tag/:containerName/:tag", async (req, res) => { try { await removeTagFromContainer(containerName, tag); res.json({ success: true, message: "Tag removed successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -476,7 +477,7 @@ router.delete("/unpin/:containerName", async (req, res) => { try { await unpinContainer(containerName); res.json({ success: true, message: "Container unpinned successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -528,7 +529,7 @@ router.delete("/remove-link/:containerName", async (req, res) => { try { await removeLink(containerName); res.json({ success: true, message: "Link removed successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); @@ -580,9 +581,9 @@ router.delete("/remove-icon/:containerName", async (req, res) => { try { await removeIcon(containerName); res.json({ success: true, message: "Icon removed successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ success: false, error: error.message }); } }); -module.exports = router; +export default router; diff --git a/routes/getter/routes.js b/src/routes/getter/routes.ts similarity index 68% rename from routes/getter/routes.js rename to src/routes/getter/routes.ts index 6c5fbc0d..0f9883f9 100644 --- a/routes/getter/routes.js +++ b/src/routes/getter/routes.ts @@ -1,16 +1,15 @@ -const extractRelevantData = require("../../utils/extractHostData"); -const express = require("express"); -const router = express.Router(); -const { - writeOfflineLog, - readOfflineLog, -} = require("../../utils/writeOfflineLog"); -const { getDockerClient } = require("../../utils/dockerClient"); -const { fetchAllContainers } = require("../../utils/containerService"); -const { getCurrentSchedule } = require("../../controllers/scheduler"); -const logger = require("../../utils/logger"); -const path = require("path"); -const fs = require("fs"); +import extractRelevantData from "../../utils/extractHostData"; +import { Router, Request, Response } from "express"; +import { writeOfflineLog, readOfflineLog } from "../../utils/writeOfflineLog"; +import getDockerClient from "../../utils/dockerClient"; +import fetchAllContainers from "../../utils/containerService"; +import { getCurrentSchedule } from "../../controllers/scheduler"; +import logger from "../../utils/logger"; +import fs from "fs"; +import checkReachability from "../../utils/connectionChecker"; +const configPath = "./src/data/dockerConfig.json"; +const router = Router(); +const userConf = "./src/data/user.conf"; /** * @swagger @@ -32,12 +31,65 @@ const fs = require("fs"); * type: string * example: ["local", "remote1"] */ +router.get("/hosts", (req: Request, res: Response) => { + logger.info(`Fetching config: ${configPath}`); + try { + const rawData = fs.readFileSync(configPath, "utf-8"); + const config = JSON.parse(rawData); + + if (!config.hosts) { + throw new Error("No hosts defined in configuration."); + } -router.get("/hosts", (req, res) => { - const config = require("../../config/dockerConfig.json"); - const hosts = config.hosts.map((host) => host.name); - logger.info("Fetching all available Docker hosts"); - res.status(200).json({ hosts }); + const hosts = config.hosts.map((host: any) => host.name); + logger.debug("Fetching all available Docker hosts"); + res.status(200).json({ hosts }); + } catch (error: any) { + logger.error("Error fetching hosts: " + error.message); + res.status(500).json({ error: "Failed to fetch Docker hosts" }); + } +}); + +/** + * @swagger + * /api/system: + * get: + * summary: Retrieve system configuration details + * tags: [Misc] + * responses: + * 200: + * description: A JSON object containing the system configuration details. + * content: + * application/json: + * schema: + * type: object + * description: The parsed configuration details. + * 500: + * description: An error occurred while fetching the system configuration. + * content: + * application/json: + * schema: + * type: object + * properties: + * error: + * type: string + * description: Error message detailing the issue encountered. + */ +router.get("/system", (req: Request, res: Response) => { + logger.info(`Fetching ${userConf}`); + + try { + const rawData = fs.readFileSync(userConf, "utf8"); + const config = JSON.parse(rawData); + + if (!config) { + res.status(500).json({ error: `Error received empty ${userConf}` }); + } + res.status(200).json(config); + } catch (error: any) { + logger.error(`Could not fetch ${userConf}: ${error}`); + res.status(500).json({ error: `Failed to fetch ${userConf}` }); + } }); /** @@ -81,7 +133,7 @@ router.get("/hosts", (req, res) => { * type: string * description: Error message detailing the issue encountered. */ -router.get("/host/:hostName/stats", async (req, res) => { +router.get("/host/:hostName/stats", async (req: Request, res: Response) => { const hostName = req.params.hostName; logger.info(`Fetching stats for host: ${hostName}`); if (process.env.OFFLINE === "true") { @@ -96,7 +148,7 @@ router.get("/host/:hostName/stats", async (req, res) => { writeOfflineLog(JSON.stringify(relevantData)); res.status(200).json(relevantData); - } catch (error) { + } catch (error: any) { logger.error( `Error fetching stats for host: ${hostName} - ${error.message || "Unknown error"}`, ); @@ -180,12 +232,13 @@ router.get("/host/:hostName/stats", async (req, res) => { * type: string * description: Error message detailing the issue encountered. */ -router.get("/containers", async (req, res) => { +router.get("/containers", async (req: Request, res: Response) => { logger.info("Fetching all containers across all hosts"); try { const allContainerData = await fetchAllContainers(); + logger.debug("Fetched /api/containers"); res.status(200).json(allContainerData); - } catch (error) { + } catch (error: any) { logger.error(`Error fetching containers: ${error.message}`); res.status(500).json({ error: "Failed to fetch containers" }); } @@ -216,13 +269,13 @@ router.get("/containers", async (req, res) => { * type: string * description: Error message detailing the issue encountered. */ -router.get("/config", async (req, res) => { - const configPath = path.join(__dirname, "../../config/dockerConfig.json"); +router.get("/config", async (req: Request, res: Response) => { try { const rawData = fs.readFileSync(configPath); const jsonData = JSON.parse(rawData.toString()); + logger.debug("Fetching /api/config"); res.status(200).json(jsonData); - } catch (error) { + } catch (error: any) { logger.error("Error loading dockerConfig.json: " + error.message); res.status(500).json({ error: "Failed to load Docker configuration" }); } @@ -246,8 +299,9 @@ router.get("/config", async (req, res) => { * type: integer * description: Current fetch interval in seconds. */ -router.get("/current-schedule", (req, res) => { +router.get("/current-schedule", (req: Request, res: Response) => { const currentSchedule = getCurrentSchedule(); + logger.debug("Fetching current shedule"); res.json(currentSchedule); }); @@ -255,23 +309,39 @@ router.get("/current-schedule", (req, res) => { * @swagger * /api/status: * get: - * summary: Check server status + * summary: Check the DockStatAPI and docker socket status of each host * tags: [Misc] - * description: Returns a 200 status with an "up" message to indicate the server is up and running. Used for Healthchecks + * description: Returns the status of the backend and online components, indicating which nodes are reachable or offline. * responses: * 200: - * description: Server is running + * description: Server and backend status * content: * application/json: * schema: * type: object * properties: - * status: - * type: string - * example: "up" + * backendReachable: + * type: boolean + * example: true + * online: + * type: object + * properties: + * Host-1: + * type: boolean + * example: true + * Host-2: + * type: boolean + * example: false */ -router.get("/status", (req, res) => { - res.status(200).json({ status: "up" }); + +router.get("/status", async (req: Request, res: Response) => { + logger.debug("Fetching /api/status"); + try { + let jsonData = await checkReachability(); + res.status(200).json(jsonData); + } catch (error: any) { + logger.error(`Error while fetching data: ${error}`); + } }); /** @@ -316,19 +386,27 @@ router.get("/status", (req, res) => { * type: string * description: Error message */ -router.get("/frontend-config", (req, res) => { - const configPath = path.join( - __dirname, - "../../data/frontendConfiguration.json", - ); +router.get("/frontend-config", (req: Request, res: Response) => { + const configPath: string = "./src/data/frontendConfiguration.json"; + + fs.stat(configPath, (exists) => { + if (exists == null) { + logger.debug(`${configPath} exists, trying to read it`); + } else if (exists.code === "ENOENT") { + logger.warn(`${configPath} doesn't exist, trying to create it`); + fs.promises.writeFile(configPath, JSON.stringify([], null, 2), "utf-8"); + } + }); + try { const rawData = fs.readFileSync(configPath); const jsonData = JSON.parse(rawData.toString()); + res.status(200).json(jsonData); - } catch (error) { + } catch (error: any) { logger.error("Error loading frontendConfiguration.json: " + error.message); res.status(500).json({ error: "Failed to load Frontend configuration" }); } }); -module.exports = router; +export default router; diff --git a/src/routes/highavailability/routes.ts b/src/routes/highavailability/routes.ts new file mode 100644 index 00000000..bc4cb794 --- /dev/null +++ b/src/routes/highavailability/routes.ts @@ -0,0 +1,92 @@ +// File: /src/routes/ha/routes.ts +import { Router, Request, Response } from "express"; +import logger from "../../utils/logger"; +import { + readConfig, + synchronizeFilesWithNodes, + prepareFilesForSync, + HighAvailabilityConfig, + ensureFileExists, +} from "../../controllers/highAvailability"; + +interface SyncRequestBody { + files: Record; +} + +const router = Router(); + +/** + * @swagger + * /ha/config: + * get: + * summary: Retrieve the High Availability Config + * tags: [High Availability] + * responses: + * 200: + * description: A JSON object containing the config. + */ +router.get("/config", async (req: Request, res: Response) => { + logger.info("Getting the HA-Config"); + const data = await readConfig(); + res.status(200).json(data); +}); + +/** + * @swagger + * /ha/sync: + * post: + * summary: Synchronize configuration files from master node. + * tags: [High Availability] + * responses: + * 200: + * description: Files synchronized successfully. + */ +router.post( + "/sync", + async ( + req: Request<{}, {}, SyncRequestBody>, + res: Response, + ): Promise => { + try { + const { files } = req.body; + + if (!files || typeof files !== "object") { + const errorMsg = + "Invalid request: 'files' object is missing or invalid."; + logger.error(errorMsg); + res.status(400).json({ message: errorMsg }); + return; + } + + logger.info("Received synchronization request from master node."); + + for (const [filePath, content] of Object.entries(files)) { + await ensureFileExists(filePath, content); + } + + logger.info("Synchronization completed successfully."); + res.status(200).json({ message: "Synchronization completed." }); + } catch (error) { + logger.error(`Error during synchronization: ${(error as Error).message}`); + res.status(500).json({ message: "Synchronization failed." }); + } + }, +); + +/** + * @swagger + * /ha/prepare-sync: + * get: + * summary: Prepare files for synchronization. + * tags: [High Availability] + * responses: + * 200: + * description: A JSON object containing files to sync. + */ +router.get("/prepare-sync", async (req: Request, res: Response) => { + logger.info("Preparing files for synchronization."); + const fileData = await prepareFilesForSync(); + res.status(200).json(fileData); +}); + +export default router; diff --git a/routes/notifications/routes.js b/src/routes/notifications/routes.ts similarity index 73% rename from routes/notifications/routes.js rename to src/routes/notifications/routes.ts index 592ab638..262d48f3 100644 --- a/routes/notifications/routes.js +++ b/src/routes/notifications/routes.ts @@ -1,13 +1,26 @@ -const express = require("express"); -const router = express.Router(); -const logger = require("../../utils/logger"); -const path = require("path"); -const fs = require("fs"); -const notify = require("../../utils/notifications/_notify"); -const dataTemplate = path.join( - __dirname, - "../../utils/notifications/data/template.json", -); +import { Request, Response, Router } from "express"; +import logger from "../../utils/logger"; +import fs from "fs"; +import notify from "../../utils/notifications/_notify"; +const dataTemplate = "./src/data/template.json"; +const router = Router(); + +/////////// +// Will be moved! + +interface TemplateData { + text: string; +} + +function isTemplateData(data: any): data is TemplateData { + return ( + data !== null && typeof data === "object" && typeof data.text === "string" + ); +} + +// Will be moved +/////////// + /** * @swagger * /notification-service/get-template: @@ -39,7 +52,7 @@ const dataTemplate = path.join( * type: string * description: Error message */ -router.get("/get-template", (req, res) => { +router.get("/get-template", (req: Request, res: Response) => { fs.readFile(dataTemplate, "utf-8", (error, data) => { if (error) { logger.error("Errored opening:", error); @@ -84,23 +97,28 @@ router.get("/get-template", (req, res) => { * type: string * description: Error message */ -router.post("/set-template", (req, res) => { - const newData = req.body; +router.post("/set-template", (req: Request, res: Response): void => { + const newData: TemplateData = req.body; + + if (!isTemplateData(newData)) { + res.status(400).json({ + message: "Invalid input format. Expected JSON with a 'text' field.", + }); + return; + } - fs.writeFile( - dataTemplate, - JSON.stringify(newData, null, 2), - "utf-8", - (error) => { - if (error) { - logger.error("Errored writing to file:", error); - return res - .status(500) - .json({ message: `Error writing to file: ${error}` }); - } + fs.promises + .writeFile(dataTemplate, JSON.stringify(newData, null, 2), "utf-8") + .then(() => { + logger.info("Template updated successfully."); res.json({ message: "Template updated successfully." }); - }, - ); + }) + .catch((error) => { + logger.error("Error writing to file: " + error.message); + res + .status(500) + .json({ message: `Error writing to file: ${error.message}` }); + }); }); /** @@ -146,14 +164,14 @@ router.post("/set-template", (req, res) => { * message: * type: string */ -router.post("/test/:type/:containerId", async (req, res) => { +router.post("/test/:type/:containerId", async (req: Request, res: Response) => { const { type, containerId } = req.params; try { await notify(type, containerId); res.json({ success: true, message: `Sent test notification to ${type}` }); - } catch (error) { + } catch (error: any) { res.json({ success: false, message: `Errored: ${error}` }); } }); -module.exports = router; +export default router; diff --git a/src/routes/setter/routes.ts b/src/routes/setter/routes.ts new file mode 100644 index 00000000..fcffeef9 --- /dev/null +++ b/src/routes/setter/routes.ts @@ -0,0 +1,180 @@ +import { setFetchInterval, parseInterval } from "../../controllers/scheduler"; +import logger from "../../utils/logger"; +import { Router, Request, Response } from "express"; +import fs from "fs"; + +const router = Router(); +const configPath: string = "./src/data/dockerConfig.json"; + +interface Host { + name: string; + url: string; + port: string; +} + +interface DockerConfig { + hosts: Host[]; +} + +/** + * @swagger + * /conf/addHost: + * put: + * summary: Add a new host to the Docker configuration + * tags: [Configuration] + * parameters: + * - name: name + * in: query + * required: true + * description: The name of the new host. + * - name: url + * in: query + * required: true + * description: The URL of the new host. + * - name: port + * in: query + * required: true + * description: The port of the new host. + * responses: + * 200: + * description: Host added successfully. + * 400: + * description: Bad request, invalid input. + * 500: + * description: An error occurred while adding the host. + */ + +router.put( + "/addHost", + async ( + req: Request< + unknown, + unknown, + unknown, + { name: string; url: string; port: string } + >, + res: Response, + ): Promise => { + const { name, url, port } = req.query; + + if (!name || !url || !port) { + res.status(400).json({ error: "Name, Port, and URL are required." }); + return; + } + + try { + const config: DockerConfig = JSON.parse( + fs.readFileSync(configPath, "utf-8"), + ); + + if (config.hosts.some((host) => host.name === name)) { + res.status(400).json({ error: "Host already exists." }); + return; + } + + config.hosts.push({ name, url, port }); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + logger.info(`Added new host: ${name}`); + res.status(200).json({ message: "Host added successfully." }); + } catch (error: unknown) { + const err = error as Error; + logger.error("Error adding host: " + err.message); + res.status(500).json({ error: "Failed to add host." }); + } + }, +); + +/** + * @swagger + * /conf/scheduler: + * put: + * summary: Set fetch interval for data fetching + * tags: [Configuration] + * parameters: + * - name: interval + * in: query + * required: true + * description: The new interval for fetching data, e.g., "6h 20m", "300s". + * responses: + * 200: + * description: Fetch interval set successfully. + * 400: + * description: Invalid interval format or out of range. + */ +router.put("/scheduler", (req: any, res: any) => { + const interval = req.query.interval as string; + + try { + const newInterval = parseInterval(interval); + + if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { + return res + .status(400) + .json({ error: "Interval must be between 5 minutes and 6 hours." }); + } + + setFetchInterval(newInterval); + res.json({ message: `Fetch interval set to ${interval}.` }); + } catch (error: unknown) { + const err = error as Error; + logger.error("Error setting fetch interval: " + err.message); + res.status(400).json({ error: "Invalid interval format." }); + } +}); + +/** + * @swagger + * /conf/removeHost: + * delete: + * summary: Remove a host from the Docker configuration + * tags: [Configuration] + * parameters: + * - name: hostName + * in: query + * required: true + * description: The name of the host to remove. + * responses: + * 200: + * description: Host removed successfully. + * 404: + * description: Host not found. + * 500: + * description: An error occurred while removing the host. + */ +router.delete("/removeHost", (req: Request, res: Response): void => { + const hostName = req.query.hostName as string; + + if (!hostName) { + res.status(400).json({ error: "Host name is required." }); + return; + } + + fs.promises + .readFile(configPath, "utf-8") + .then((rawData) => { + const config: DockerConfig = JSON.parse(rawData); + const hostIndex = config.hosts.findIndex( + (host) => host.name === hostName, + ); + + if (hostIndex === -1) { + res.status(404).json({ error: "Host not found." }); + return; + } + + config.hosts.splice(hostIndex, 1); + + return fs.promises + .writeFile(configPath, JSON.stringify(config, null, 2)) + .then(() => { + logger.info(`Removed host: ${hostName}`); + res.status(200).json({ message: "Host removed successfully." }); + }); + }) + .catch((error) => { + logger.error("Error removing host: " + (error as Error).message); + res.status(500).json({ error: "Failed to remove host." }); + }); +}); + +export default router; diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 00000000..4853204b --- /dev/null +++ b/src/server.ts @@ -0,0 +1,17 @@ +import express from "express"; +import logger from "./utils/logger"; +import initializeApp from "./init"; +import { startMasterNode } from "./controllers/highAvailability"; +import writeUserConf from "./config/hostsystem"; + +const app = express(); +const PORT: number = 9876; + +writeUserConf(); +initializeApp(app); + +app.listen(PORT, () => { + logger.info(`Server is running on http://localhost:${PORT}`); + logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs`); + startMasterNode(); +}); diff --git a/src/utils/connectionChecker.ts b/src/utils/connectionChecker.ts new file mode 100644 index 00000000..c61ffebd --- /dev/null +++ b/src/utils/connectionChecker.ts @@ -0,0 +1,77 @@ +import * as fs from "fs"; +import * as net from "net"; +import logger from "../config/loggerConfig"; + +const filePath: string = "./src/data/dockerConfig.json"; + +interface Host { + name: string; + url: string; + port: string; +} + +interface StatusResponse { + ApiReachable: boolean; + online: { [key: string]: boolean }; +} + +async function checkHostStatus(hosts: Host[]): Promise { + const results: { [key: string]: boolean } = {}; + for (const host of hosts) { + const { name, url, port } = host; + + const isOnline = await checkPort(url, parseInt(port, 10)); + + results[name] = !!isOnline; + + if (results[name] == true) { + logger.debug(`${host.url}:${port} is online`); + } else { + logger.debug(`${host.url}:${port} is unreachable`); + } + } + + return { + ApiReachable: true, + online: results, + }; +} + +function checkPort(host: string, port: number): Promise { + return new Promise((resolve) => { + const socket = new net.Socket(); + socket.setTimeout(3000); + + socket.on("connect", () => { + socket.end(); + resolve(true); + }); + + socket.on("timeout", () => { + socket.destroy(); + resolve(false); + }); + + socket.on("error", () => { + socket.destroy(); + resolve(false); + }); + + socket.connect(port, host); + }); +} + +async function checkReachability(): Promise { + try { + const data = fs.readFileSync(filePath, "utf-8"); + const parsedData = JSON.parse(data); + const hosts: Host[] = parsedData.hosts; + return await checkHostStatus(hosts); + + } catch (error: any) { + logger.error(`Error reading file: ${error}`); + return undefined; + } +} + +export default checkReachability; diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts new file mode 100644 index 00000000..afc035a1 --- /dev/null +++ b/src/utils/containerService.ts @@ -0,0 +1,134 @@ +import logger from "./logger"; +import { ContainerInfo, ContainerStats, ContainerInspectInfo } from "dockerode"; +import getDockerClient from "./dockerClient"; +import fs from "fs"; +const configPath = "./src/data/dockerConfig.json"; + +interface HostConfig { + name: string; + [key: string]: any; +} + +interface ContainerData { + name: string; + id: string; + hostName: string; + state: string; + cpu_usage: number; + mem_usage: number; + mem_limit: number; + net_rx: number; + net_tx: number; + current_net_rx: number; + current_net_tx: number; + networkMode: string; +} + +interface AllContainerData { + [hostName: string]: ContainerData[] | { error: string }; +} + +function loadConfig() { + try { + if (!fs.existsSync(configPath)) { + logger.warn(`Config file not found. Creating an empty file at ${configPath}`); + fs.writeFileSync(configPath, JSON.stringify({ "hosts": [] }, null, 2), "utf-8"); + } + + const configData = fs.readFileSync(configPath, "utf-8"); + logger.debug("Loaded " + configPath); + return JSON.parse(configData); + } catch (error: any) { + logger.error(`Failed to load config: ${error.message}`); + return null; + } +} + +async function fetchAllContainers(): Promise { + const config = loadConfig(); + if (!config || !config.hosts) { + logger.error("Invalid or missing host configuration."); + return {}; + } + + const allContainerData: AllContainerData = {}; + + for (const hostConfig of config.hosts as HostConfig[]) { + const hostName = hostConfig.name; + try { + const docker: any = getDockerClient(hostName); + logger.debug(`Now processing: ${hostName}`); + const containers: ContainerInfo[] = await docker.listContainers({ + all: true, + }); + + allContainerData[hostName] = await Promise.all( + containers.map(async (container) => { + try { + const containerInstance = docker.getContainer(container.Id); + const containerInfo: ContainerInspectInfo = + await containerInstance.inspect(); + const containerStats: ContainerStats = + await containerInstance.stats({ stream: false }); + + const cpuDelta = + containerStats.cpu_stats.cpu_usage.total_usage - + containerStats.precpu_stats.cpu_usage.total_usage; + const systemCpuDelta = + containerStats.cpu_stats.system_cpu_usage - + containerStats.precpu_stats.system_cpu_usage; + const cpuUsage = + systemCpuDelta > 0 + ? (cpuDelta / systemCpuDelta) * + containerStats.cpu_stats.online_cpus + : 0; + + return { + name: container.Names[0].replace("/", ""), + id: container.Id, + hostName, + state: container.State, + cpu_usage: cpuUsage * 1000000000, + mem_usage: containerStats.memory_stats.usage, + mem_limit: containerStats.memory_stats.limit, + net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + current_net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + networkMode: containerInfo.HostConfig.NetworkMode || "unknown", + }; + } catch (containerError: any) { + logger.error( + `Error fetching details for container ID: ${container.Id} on host: ${hostName} - ${containerError.message}`, + ); + return { + name: container.Names[0].replace("/", ""), + id: container.Id, + hostName, + state: container.State, + cpu_usage: 0, + mem_usage: 0, + mem_limit: 0, + net_rx: 0, + net_tx: 0, + current_net_rx: 0, + current_net_tx: 0, + networkMode: "unknown", + }; + } + }), + ); + } catch (error: any) { + logger.error( + `Error fetching containers for host: ${hostName} - ${error.message}. Stack: ${error.stack}`, + ); + allContainerData[hostName] = { + error: `Error fetching containers: ${error.message}`, + }; + } + } + + return allContainerData; +} + +export default fetchAllContainers; diff --git a/src/utils/createDependencyGraph.sh b/src/utils/createDependencyGraph.sh new file mode 100755 index 00000000..c8229992 --- /dev/null +++ b/src/utils/createDependencyGraph.sh @@ -0,0 +1,37 @@ +#!/bin/bash +cd src || exit 1 +TMP=$(mktemp) + +cat ./server.ts | grep "./routes" | awk '{print $2,$4}' > $TMP + +spawn_worker(){ + local line="$1" + local target_route="$(echo "$line" | cut -d '"' -f2).ts" + local route=$(echo "$line" | awk '{print $1}') + + echo -e "\nRoute: $route \n${target_route}" + + npx depcruise \ + -p cli-feedback \ + -T mermaid \ + -x "../node_modules|logger|.dependency-cruiser|path|fs|net" \ + -f ./misc/dependencyGraphs/mermaid-${route}.txt \ + ${target_route} || exit 1 +} + +while read line; do + spawn_worker "$line" & +done < <(cat $TMP) + +npx depcruise \ + -p cli-feedback \ + -T mermaid \ + -x "../node_modules|logger|.dependency-cruiser|path|fs" \ + -f ./misc/dependencyGraphs/mermaid-all.txt \ + ./server.ts || exit 1 + +wait + +echo -e "\n========\n\n DONE\n\n========" + +exit 0 diff --git a/src/utils/dockerClient.ts b/src/utils/dockerClient.ts new file mode 100644 index 00000000..4cb3f70c --- /dev/null +++ b/src/utils/dockerClient.ts @@ -0,0 +1,54 @@ +// src/utils/dockerClient.ts +import Docker from "dockerode"; +import fs from "fs"; +import logger from "./logger"; + +interface DockerHostConfig { + name: string; + url: string; + port?: number; +} + +interface DockerConfig { + hosts: DockerHostConfig[]; +} + +function loadDockerConfig(): DockerConfig { + const configPath = "./src/data/dockerConfig.json"; + try { + const rawData = fs.readFileSync(configPath, "utf-8"); + logger.debug("Refreshed DockerConfig.json"); + return JSON.parse(rawData) as DockerConfig; + } catch (error: any) { + logger.error( + "Error loading dockerConfig.json: " + (error as Error).message, + ); + throw new Error("Failed to load Docker configuration"); + } +} + +function createDockerClient(hostConfig: DockerHostConfig): Docker { + logger.info( + `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}`, + ); + return new Docker({ + host: hostConfig.url, + port: hostConfig.port || 2375, + protocol: "http", + }); +} + +const getDockerClient = (hostName: string): Docker => { + logger.debug(`Getting Docker Client for ${hostName}`); + const config = loadDockerConfig(); + const hostConfig = config.hosts.find((host) => host.name === hostName); + + if (!hostConfig) { + const errorMsg = `Docker host ${hostName} not found in configuration`; + logger.error(errorMsg); + throw new Error(errorMsg); + } + return createDockerClient(hostConfig); +}; + +export default getDockerClient; diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts new file mode 100644 index 00000000..b6192ea7 --- /dev/null +++ b/src/utils/extractHostData.ts @@ -0,0 +1,57 @@ +interface Component { + Name: string; + Version: string; +} + +interface JsonData { + hostName: string; + info: { + ID: string; + Containers: number; + ContainersRunning: number; + ContainersPaused: number; + ContainersStopped: number; + Images: number; + OperatingSystem: string; + KernelVersion: string; + Architecture: string; + MemTotal: number; + NCPU: number; + }; + version: { + Components: Component[]; + }; +} + +type ComponentMap = Record; + +// Export the function with type annotations +function extractRelevantData(jsonData: JsonData) { + return { + hostName: jsonData.hostName, + info: { + ID: jsonData.info.ID, + Containers: jsonData.info.Containers, + ContainersRunning: jsonData.info.ContainersRunning, + ContainersPaused: jsonData.info.ContainersPaused, + ContainersStopped: jsonData.info.ContainersStopped, + Images: jsonData.info.Images, + OperatingSystem: jsonData.info.OperatingSystem, + KernelVersion: jsonData.info.KernelVersion, + Architecture: jsonData.info.Architecture, + MemTotal: jsonData.info.MemTotal, + NCPU: jsonData.info.NCPU, + }, + version: { + Components: jsonData.version.Components.reduce( + (acc, component) => { + acc[component.Name] = component.Version; + return acc; + }, + {}, + ), + }, + }; +} + +export default extractRelevantData; diff --git a/utils/logger.js b/src/utils/logger.ts similarity index 52% rename from utils/logger.js rename to src/utils/logger.ts index 9d25e5d6..e69955a9 100644 --- a/utils/logger.js +++ b/src/utils/logger.ts @@ -1,7 +1,7 @@ -const winston = require("winston"); -const loggerConfig = require("../config/loggerConfig"); +import winston, { transport } from "winston"; +import loggerConfig from "../config/loggerConfig"; -const transports = [new winston.transports.Console()]; +const transports: transport[] = [new winston.transports.Console()]; transports.push( new winston.transports.File({ @@ -15,4 +15,4 @@ const logger = winston.createLogger({ transports, }); -module.exports = logger; +export default logger; diff --git a/src/utils/notifications/_notify.ts b/src/utils/notifications/_notify.ts new file mode 100644 index 00000000..018b3dce --- /dev/null +++ b/src/utils/notifications/_notify.ts @@ -0,0 +1,85 @@ +import logger from "../../utils/logger"; +import { telegramNotification } from "./telegram"; +import { slackNotification } from "./slack"; +import { discordNotification } from "./discord"; +import { emailNotification } from "./email"; +import { whatsappNotification } from "./whatsapp"; +import { pushbulletNotification } from "./pushbullet"; +import { pushoverNotification } from "./pushover"; +import path from "path"; + +async function loadCustomNotification(scriptPath: string, containerId: string) { + try { + const absolutePath = path.resolve(__dirname, "./custom", scriptPath); + const customModule = await import(absolutePath); + + if (typeof customModule.default !== "function") { + const errorMsg = `The custom notification script at ${scriptPath} does not export a default function.`; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + logger.debug(`Executing custom notification script: ${scriptPath}`); + await customModule.default(containerId); + } catch (error: any) { + logger.error( + `Failed to execute custom notification script (${scriptPath}): ${error.message}`, + ); + throw error; + } +} + +async function notify(type: string, containerId: string) { + if (!containerId) { + logger.error("Container ID is required."); + throw new Error("Container ID is required."); + } + + if (type.startsWith("custom/")) { + const scriptName = type.split("/")[1]; + if (!scriptName) { + const errorMsg = "Custom notification script name is invalid."; + logger.error(errorMsg); + throw new Error(errorMsg); + } + await loadCustomNotification(`${scriptName}.js`, containerId); + return; + } + + switch (type) { + case "telegram": + logger.debug("Sending Telegram notification..."); + await telegramNotification(containerId); + break; + case "slack": + logger.debug("Sending Slack notification..."); + await slackNotification(containerId); + break; + case "discord": + logger.debug("Sending Discord notification..."); + await discordNotification(containerId); + break; + case "email": + logger.debug("Sending Email notification..."); + await emailNotification(containerId); + break; + case "whatsapp": + logger.debug("Sending WhatsApp notification..."); + await whatsappNotification(containerId); + break; + case "pushbullet": + logger.debug("Sending Pushbullet notification..."); + await pushbulletNotification(containerId); + break; + case "pushover": + logger.debug("Sending Pushover notification..."); + await pushoverNotification(containerId); + break; + default: + const errorMsg = "Unknown notification type."; + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +export default notify; diff --git a/utils/notifications/data/template.js b/src/utils/notifications/_template.ts similarity index 51% rename from utils/notifications/data/template.js rename to src/utils/notifications/_template.ts index 9a090f61..ecc327e1 100644 --- a/utils/notifications/data/template.js +++ b/src/utils/notifications/_template.ts @@ -1,36 +1,41 @@ -const fs = require("fs"); -const path = require("path"); -const logger = require("../../logger"); +import fs from "fs"; +import logger from "../logger"; +const templatePath: string = "./src/data/template.json"; +const containersPath: string = "./src/data/states.json"; -const templatePath = path.join(__dirname, "template.json"); -const containersPath = path.join(__dirname, "../../../data/states.json"); +interface Template { + "text": string +} function getTemplate() { try { const data = fs.readFileSync(templatePath, "utf8"); return JSON.parse(data); - } catch (error) { - console.error("Failed to load template:", error); + } catch (error: any) { + logger.error("Failed to load template:", error); return null; } } -function setTemplate(newTemplate) { +function setTemplate(newTemplate: string) { try { fs.writeFileSync( templatePath, JSON.stringify(newTemplate, null, 2), "utf8", ); - logger.log("Template updated successfully"); - } catch (error) { + logger.debug("Template updated successfully"); + } catch (error: any) { logger.error("Failed to update template:", error); } } -function renderTemplate(containerId) { - const template = getTemplate(); - if (!template) return null; +function renderTemplate(containerId: string) { + const template: Template = getTemplate(); + if (!template) { + logger.error("Template is missing or not a string"); + return null; + } try { const data = fs.readFileSync(containersPath, "utf8"); @@ -38,12 +43,12 @@ function renderTemplate(containerId) { let containerData = null; for (const host in containers) { - containerData = containers[host].find((c) => c.id === containerId); + containerData = containers[host].find((c: any) => c.id === containerId); if (containerData) break; } if (!containerData) { - console.error(`Container with ID ${containerId} not found`); + logger.error(`Container with ID ${containerId} not found`); return null; } @@ -51,12 +56,13 @@ function renderTemplate(containerId) { return Object.keys(containerData).reduce( (text, key) => text.replace(new RegExp(`{{${key}}}`, "g"), containerData[key]), - template.message, + template.text, ); - } catch (error) { + } catch (error: any) { logger.error("Failed to load containers:", error); return null; } } -module.exports = { getTemplate, setTemplate, renderTemplate }; + +export { getTemplate, setTemplate, renderTemplate }; diff --git a/src/utils/notifications/discord.ts b/src/utils/notifications/discord.ts new file mode 100644 index 00000000..24aaf905 --- /dev/null +++ b/src/utils/notifications/discord.ts @@ -0,0 +1,55 @@ +import * as https from 'https'; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const discord_webhook_url: string | undefined = process.env.DISCORD_WEBHOOK_URL; + +export async function discordNotification(containerId: string): Promise { + const discord_message: string | null = renderTemplate(containerId); + if (!discord_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!discord_webhook_url) { + logger.error("Discord webhook URL is not set."); + return; + } + + const postData = JSON.stringify({ + content: discord_message, + }); + + const url = new URL(discord_webhook_url); + + const options = { + hostname: url.hostname, + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode !== 200) { + logger.error(`Discord API error: ${data}`); + } + }); + }); + + req.on('error', (error) => { + logger.error("Error sending Discord message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/notifications/email.ts b/src/utils/notifications/email.ts new file mode 100644 index 00000000..fbefbab6 --- /dev/null +++ b/src/utils/notifications/email.ts @@ -0,0 +1,46 @@ +import { SendMailOptions, createTransport } from "nodemailer"; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const email_sender: string | undefined = process.env.EMAIL_SENDER; +const email_recipient: string | undefined = process.env.EMAIL_RECIPIENT; +const email_password: string | undefined = process.env.EMAIL_PASSWORD; +const email_service: string | undefined = process.env.EMAIL_SERVICE; + +export async function emailNotification(containerId: string) { + // Validate email configuration parameters + if (!email_sender || !email_recipient || !email_password || !email_service) { + logger.error( + "Email notification failed: Missing configuration parameters. " + + "Please ensure EMAIL_SENDER, EMAIL_RECIPIENT, EMAIL_PASSWORD, and EMAIL_SERVICE are set in environment variables.", + ); + return; + } + + const email_message: string | null = renderTemplate(containerId); + if (!email_message) { + logger.error("Failed to create notification message."); + return; + } + + const transporter = createTransport({ + service: email_service, + auth: { + user: email_sender, + pass: email_password, + }, + }); + + const mailOptions: SendMailOptions = { + from: email_sender, + to: email_recipient, + subject: "DockStat", + text: email_message, + }; + + try { + await transporter.sendMail(mailOptions); + } catch (error: any) { + logger.error("Error sending email:", error); + } +} diff --git a/src/utils/notifications/pushbullet.ts b/src/utils/notifications/pushbullet.ts new file mode 100644 index 00000000..f008e68c --- /dev/null +++ b/src/utils/notifications/pushbullet.ts @@ -0,0 +1,59 @@ +import * as https from "https"; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const pushbullet_access_token: string | undefined = + process.env.PUSHBULLET_ACCESS_TOKEN; + +export async function pushbulletNotification( + containerId: string, +): Promise { + const pushbullet_message: string | null = renderTemplate(containerId); + if (!pushbullet_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!pushbullet_access_token) { + logger.error("Pushbullet access token is not set."); + return; + } + + const postData = JSON.stringify({ + type: "note", + title: "Container Notification", + body: pushbullet_message, + }); + + const options = { + hostname: "api.pushbullet.com", + path: "/v2/pushes", + method: "POST", + headers: { + "Access-Token": pushbullet_access_token, + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ""; + + res.on("data", (chunk) => { + data += chunk; + }); + + res.on("end", () => { + if (res.statusCode !== 200) { + logger.error(`Pushbullet API error: ${data}`); + } + }); + }); + + req.on("error", (error) => { + logger.error("Error sending Pushbullet message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/notifications/pushover.ts b/src/utils/notifications/pushover.ts new file mode 100644 index 00000000..847c3296 --- /dev/null +++ b/src/utils/notifications/pushover.ts @@ -0,0 +1,56 @@ +import * as https from 'https'; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const pushover_user_key: string | undefined = process.env.PUSHOVER_USER_KEY; +const pushover_api_token: string | undefined = process.env.PUSHOVER_API_TOKEN; + +export async function pushoverNotification(containerId: string): Promise { + const pushover_message: string | null = renderTemplate(containerId); + if (!pushover_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!pushover_api_token || !pushover_user_key) { + logger.error("Pushover API token or user key is not set."); + return; + } + + const postData = new URLSearchParams({ + token: pushover_api_token, + user: pushover_user_key, + message: pushover_message, + }).toString(); + + const options = { + hostname: 'api.pushover.net', + path: '/1/messages.json', + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode !== 200) { + logger.error(`Pushover API error: ${data}`); + } + }); + }); + + req.on('error', (error) => { + logger.error("Error sending Pushover message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/notifications/slack.ts b/src/utils/notifications/slack.ts new file mode 100644 index 00000000..b0a8e0b4 --- /dev/null +++ b/src/utils/notifications/slack.ts @@ -0,0 +1,55 @@ +import * as https from 'https'; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const slack_webhook_url: string | undefined = process.env.SLACK_WEBHOOK_URL; + +export async function slackNotification(containerId: string): Promise { + const slack_message: string | null = renderTemplate(containerId); + if (!slack_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!slack_webhook_url) { + logger.error("Slack webhook URL is not set."); + return; + } + + const postData = JSON.stringify({ + text: slack_message, + }); + + const url = new URL(slack_webhook_url); + + const options = { + hostname: url.hostname, + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode !== 200) { + logger.error(`Slack API error: ${data}`); + } + }); + }); + + req.on('error', (error) => { + logger.error("Error sending Slack message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/notifications/telegram.ts b/src/utils/notifications/telegram.ts new file mode 100644 index 00000000..174a12e5 --- /dev/null +++ b/src/utils/notifications/telegram.ts @@ -0,0 +1,55 @@ +import * as https from 'https'; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const telegram_bot_token: string | undefined = process.env.TELEGRAM_BOT_TOKEN; +const telegram_chat_id: string | undefined = process.env.TELEGRAM_CHAT_ID; + +export async function telegramNotification(containerId: string): Promise { + const telegram_message: string | null = renderTemplate(containerId); + if (!telegram_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!telegram_bot_token || !telegram_chat_id) { + logger.error("Telegram bot token or chat ID is not set."); + return; + } + + const postData = JSON.stringify({ + chat_id: telegram_chat_id, + text: telegram_message, + }); + + const options = { + hostname: 'api.telegram.org', + path: `/bot${telegram_bot_token}/sendMessage`, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode !== 200) { + logger.error(`Telegram API error: ${data}`); + } + }); + }); + + req.on('error', (error) => { + logger.error("Error sending message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/notifications/whatsapp.ts b/src/utils/notifications/whatsapp.ts new file mode 100644 index 00000000..178f6d53 --- /dev/null +++ b/src/utils/notifications/whatsapp.ts @@ -0,0 +1,57 @@ +import * as https from 'https'; +import logger from "../logger"; +import { renderTemplate } from "./_template"; + +const whatsapp_api_url: string | undefined = process.env.WHATSAPP_API_URL; +const whatsapp_recipient: string | undefined = process.env.WHATSAPP_RECIPIENT; + +export async function whatsappNotification(containerId: string): Promise { + const whatsapp_message: string | null = renderTemplate(containerId); + if (!whatsapp_message) { + logger.error("Failed to create notification message."); + return; + } + + if (!whatsapp_api_url || !whatsapp_recipient) { + logger.error("WhatsApp API URL or recipient is not set."); + return; + } + + const postData = JSON.stringify({ + to: whatsapp_recipient, + body: whatsapp_message, + }); + + const url = new URL(whatsapp_api_url); + + const options = { + hostname: url.hostname, + path: url.pathname, + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData), + }, + }; + + const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode !== 200) { + logger.error(`WhatsApp API error: ${data}`); + } + }); + }); + + req.on('error', (error) => { + logger.error("Error sending WhatsApp message:", error); + }); + + req.write(postData); + req.end(); +} diff --git a/src/utils/removeUnusedDeps.sh b/src/utils/removeUnusedDeps.sh new file mode 100755 index 00000000..b5b68ebf --- /dev/null +++ b/src/utils/removeUnusedDeps.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +echo "Creating unused dependency list" + +TMP="$(npx depcheck --ignores @types/supports-color,ipaddr.js,dependency-cruiser,tsx,@types/bcrypt,@types/express,@types/express-handlebars,@types/node,ts-node --quiet --oneline | tail -n 1 | tr -d '\n')" + +lines=$(echo "$TMP" | tr -s ' ' '\n' | wc -l) + +if ((lines == 0)); then + echo "No unused dependencies." +else + echo + echo "Removing these unused dependencies:" + for entry in $TMP; do + echo "$entry" + done + echo +fi + + +read -n 1 -p "Delete unused dependencies? (y/n) " input +echo + +case $input in + Y|y) + COMMAND=$(echo "npm remove $TMP") + $COMMAND + exit 0 + ;; + *) + echo "Aborting" + exit 1 + ;; +esac + +exit 2 diff --git a/src/utils/swaggerDocs.ts b/src/utils/swaggerDocs.ts new file mode 100644 index 00000000..9a386ddd --- /dev/null +++ b/src/utils/swaggerDocs.ts @@ -0,0 +1,11 @@ +import swaggerUi from "swagger-ui-express"; +import swaggerJsdoc from "swagger-jsdoc"; +import swaggerConfig from "../config/swaggerConfig"; +import { Express } from "express"; + +const swaggerDocs = (app: Express) => { + const specs = swaggerJsdoc(swaggerConfig); + app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs)); +}; + +export default swaggerDocs; diff --git a/src/utils/writeOfflineLog.ts b/src/utils/writeOfflineLog.ts new file mode 100644 index 00000000..244f62e0 --- /dev/null +++ b/src/utils/writeOfflineLog.ts @@ -0,0 +1,26 @@ +import fs from "fs"; +import logger from "../utils/logger"; + +const LOG_FILE_PATH = "./logs/hostStats.json"; + +async function writeOfflineLog(message: string) { + try { + if (!fs.existsSync(LOG_FILE_PATH)) { + await fs.promises.writeFile(LOG_FILE_PATH, message); + } + } catch (error: any) { + logger.error("Error writing one time reference log: ", error); + } +} + +async function readOfflineLog() { + try { + const data = await fs.promises.readFile(LOG_FILE_PATH, "utf-8"); + logger.debug("Returning data:", data); + return data; + } catch (error: any) { + logger.error("Error reading offline log:", error); + } +} + +export { writeOfflineLog, readOfflineLog }; diff --git a/swagger/swaggerDocs.js b/swagger/swaggerDocs.js deleted file mode 100644 index 57193722..00000000 --- a/swagger/swaggerDocs.js +++ /dev/null @@ -1,10 +0,0 @@ -const swaggerUi = require("swagger-ui-express"); -const swaggerJsdoc = require("swagger-jsdoc"); -const swaggerConfig = require("../config/swaggerConfig"); - -const swaggerDocs = (app) => { - const specs = swaggerJsdoc(swaggerConfig); - app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs)); -}; - -module.exports = swaggerDocs; diff --git a/tests/main.spec.ts b/tests/main.spec.ts new file mode 100644 index 00000000..f9006426 --- /dev/null +++ b/tests/main.spec.ts @@ -0,0 +1,131 @@ +import { test, expect } from '@playwright/test'; +import ora from 'ora'; + +interface Route { + url: string; +} + +interface FrontendRoute { + url: string; + type: string; +} + +const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +test('Swagger - Auth enable and disable', async ({ page }) => { + await page.goto('http://localhost:9876/api-docs/'); + await page.getByLabel('post /auth/enable').click(); + await page.getByRole('button', { name: 'Try it out' }).click(); + await page.getByPlaceholder('password').click(); + await page.getByPlaceholder('password').fill('1'); + await page.getByRole('button', { name: 'Execute' }).click(); + await page.getByRole('button', { name: 'Authorize' }).click(); + await page.getByLabel('Value:').click(); + await page.getByLabel('Value:').fill('1'); + await page.getByLabel('Apply credentials').click(); + await page.getByRole('button', { name: 'Close' }).click(); + await page.getByLabel('post /auth/disable').click(); + await page.getByRole('button', { name: 'Try it out' }).click(); + await page.getByRole('row', { name: 'password *required (query)', exact: true }).getByPlaceholder('password').click(); + await page.getByRole('row', { name: 'password *required (query)', exact: true }).getByPlaceholder('password').fill('1'); + await page.locator('#operations-Authentication-post_auth_disable').getByRole('button', { name: 'Execute' }).click(); +}); + +test('Return 200 status code', async ({ request }) => { + await sleep(5000); + const getRoutes: Route[] = [ + { url: 'http://localhost:9876/data/latest' }, + { url: 'http://localhost:9876/data/time/24h' }, + { url: 'http://localhost:9876/api/hosts' }, + { url: 'http://localhost:9876/api/host/Fin-2/stats' }, + { url: 'http://localhost:9876/api/containers' }, + { url: 'http://localhost:9876/api/config' }, + { url: 'http://localhost:9876/api/current-schedule' }, + { url: 'http://localhost:9876/api/frontend-config' }, + { url: 'http://localhost:9876/api/status' }, + { url: 'http://localhost:9876/ha/config' }, + { url: 'http://localhost:9876/ha/prepare-sync' }, + { url: 'http://localhost:9876/notification-service/get-template' } + ]; + + for (const { url } of getRoutes) { + const spinner = ora(`Checking: ${url}`).start(); + const response = await request.get(`${url}`); + await sleep(1000); + if (response.status() === 200) { + spinner.succeed(`Checked: ${url}`); + } else { + spinner.fail(`Failed: ${url}`); + } + expect(response.status()).toBe(200); + } + + const putRoutes: Route[] = [ + { url: 'http://localhost:9876/conf/addHost?name=test&url=localhost&port=2375' }, + { url: 'http://localhost:9876/conf/scheduler?interval=300s' } + ]; + + for (const { url } of putRoutes) { + const spinner = ora(`Checking: ${url}`).start(); + const response = await request.put(`${url}`); + await sleep(1000); + if (response.status() === 200) { + spinner.succeed(`Checked: ${url}`); + } else { + spinner.fail(`Failed: ${url}`); + } + expect(response.status()).toBe(200); + } + + const data = { text: "{{name}} ({{id}}) on {{hostName}} is {{state}}." }; + + const spinner = ora('Checking: http://localhost:9876/notification-service/set-template').start(); + const response = await request.post('http://localhost:9876/notification-service/set-template', { data }); + await sleep(1000); + if (response.status() === 200) { + spinner.succeed('Checked: http://localhost:9876/notification-service/set-template'); + } else { + spinner.fail('Failed: http://localhost:9876/notification-service/set-template'); + } + expect(response.status()).toBe(200); + + // Remove test host: + const deleteSpinner = ora('Removing test host').start(); + await request.delete('http://localhost:9876/conf/removeHost?hostName=test'); + await sleep(1000); + deleteSpinner.succeed('Removed test host'); + + const frontendRoutes: FrontendRoute[] = [ + { url: 'http://localhost:9876/frontend/tag/test/test', type: "post" }, + { url: 'http://localhost:9876/frontend/pin/test', type: "post" }, + { url: 'http://localhost:9876/frontend/add-link/test/https%3A%2F%2Fexample.com', type: "post" }, + { url: 'http://localhost:9876/frontend/add-icon/test/test.png/true', type: "post" }, + { url: 'http://localhost:9876/frontend/hide/test', type: "delete" }, + { url: 'http://localhost:9876/frontend/remove-tag/test/test', type: "delete" }, + { url: 'http://localhost:9876/frontend/remove-link/test', type: "delete" }, + { url: 'http://localhost:9876/frontend/show/test', type: "post" }, + { url: 'http://localhost:9876/frontend/remove-icon/test', type: "delete" }, + { url: 'http://localhost:9876/frontend/unpin/test', type: "delete" } + ]; + + for (const { url, type } of frontendRoutes) { + const spinner = ora(`Checking: ${url}`).start(); + let response; + if (type === "post") { + response = await request.post(`${url}`); + } else if (type === "put") { + response = await request.put(`${url}`); + } else if (type === "delete") { + response = await request.delete(`${url}`); + } else { + throw new Error(`Unsupported request type: ${type}`); + } + await sleep(1000); + if (response.status() === 200) { + spinner.succeed(`Checked: ${url}`); + } else { + spinner.fail(`Failed: ${url}`); + } + expect(response.status()).toBe(200); + } +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..8fc3c320 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "outDir": "dist/src", + "module": "CommonJS", + "moduleResolution": "node", + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "esModuleInterop": true + }, + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Recommended", + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] +} \ No newline at end of file diff --git a/utils/containerService.js b/utils/containerService.js deleted file mode 100644 index eb078a55..00000000 --- a/utils/containerService.js +++ /dev/null @@ -1,63 +0,0 @@ -const config = require("../config/dockerConfig.json"); -const logger = require("./logger"); -const { getDockerClient } = require("./dockerClient"); - -async function fetchAllContainers() { - const allContainerData = {}; - - for (const hostConfig of config.hosts) { - const hostName = hostConfig.name; - try { - const docker = getDockerClient(hostName); - const containers = await docker.listContainers({ all: true }); - - allContainerData[hostName] = await Promise.all( - containers.map(async (container) => { - const containerInfo = await docker - .getContainer(container.Id) - .inspect(); - const containerStats = await docker - .getContainer(container.Id) - .stats({ stream: false }); - const cpuDelta = - containerStats.cpu_stats.cpu_usage.total_usage - - containerStats.precpu_stats.cpu_usage.total_usage; - const systemCpuDelta = - containerStats.cpu_stats.system_cpu_usage - - containerStats.precpu_stats.system_cpu_usage; - const cpuUsage = - systemCpuDelta > 0 - ? (cpuDelta / systemCpuDelta) * - containerStats.cpu_stats.online_cpus - : 0; - - return { - name: container.Names[0].replace("/", ""), - id: container.Id, - hostName: hostName, - state: container.State, - cpu_usage: cpuUsage * 1000000000, - mem_usage: containerStats.memory_stats.usage, - mem_limit: containerStats.memory_stats.limit, - net_rx: containerStats.networks?.eth0?.rx_bytes || 0, - net_tx: containerStats.networks?.eth0?.tx_bytes || 0, - current_net_rx: containerStats.networks?.eth0?.rx_bytes || 0, - current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, - networkMode: containerInfo.HostConfig.NetworkMode, - }; - }), - ); - } catch (error) { - logger.error( - `Error fetching containers for host: ${hostName} - ${error.message}`, - ); - allContainerData[hostName] = { - error: `Error fetching containers: ${error.message}`, - }; - } - } - - return allContainerData; -} - -module.exports = { fetchAllContainers }; diff --git a/utils/createDependencyGraph.sh b/utils/createDependencyGraph.sh deleted file mode 100755 index 3e75de0a..00000000 --- a/utils/createDependencyGraph.sh +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/bash - -TMP=$(mktemp) - -cat ./server.js | grep "./routes" | awk '{print $2,$4}' > $TMP - -while read line; do - target_route=$(echo "$line" | cut -d '"' -f2) - route=$(echo "$line" | awk '{print $1}') - - echo - echo "Route: $route" - echo ${target_route}.js - - - npx depcruise \ - -p cli-feedback \ - -T mermaid \ - -x "^node_modules|logger|.dependency-cruiser|path|fs" \ - -f ./misc/dependencyGraphs/mermaid-${route}.txt \ - ${target_route}.js - -done < <(cat $TMP) - -npx depcruise \ - -p cli-feedback \ - -T mermaid \ - -x "^node_modules|logger|.dependency-cruiser|path|fs" \ - -f ./misc/dependencyGraphs/mermaid-all.txt \ - ./ - -sleep 0.5 - -echo -e "\n========\n\n DONE\n\n========" diff --git a/utils/dockerClient.js b/utils/dockerClient.js deleted file mode 100644 index 3d691e50..00000000 --- a/utils/dockerClient.js +++ /dev/null @@ -1,45 +0,0 @@ -const Docker = require("dockerode"); -const fs = require("fs"); -const path = require("path"); -const logger = require("./logger"); - -// Function to dynamically load config on each request -function loadDockerConfig() { - const configPath = path.join(__dirname, "../config/dockerConfig.json"); - try { - const rawData = fs.readFileSync(configPath); - logger.debug("Refreshed DockerConfig.json"); - return JSON.parse(rawData); - } catch (error) { - logger.error("Error loading dockerConfig.json: " + error.message); - throw new Error("Failed to load Docker configuration"); - } -} - -// Function to create the Docker client using separate url and port -function createDockerClient(hostConfig) { - logger.info( - `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port}`, - ); - return new Docker({ - host: hostConfig.url, - port: hostConfig.port || 2375, // Use 2375 as default port for non-TLS - protocol: "http", // Ensure the use of http for non-TLS - }); -} - -// This function will get the Docker client based on the host configuration -const getDockerClient = (hostName) => { - logger.debug(`Getting Docker Client for ${hostName}`); - const config = loadDockerConfig(); // Dynamically load config - const hostConfig = config.hosts.find((host) => host.name === hostName); - - if (!hostConfig) { - const errorMsg = `Docker host ${hostName} not found in configuration`; - logger.error(errorMsg); - throw new Error(errorMsg); - } - return createDockerClient(hostConfig); -}; - -module.exports = { getDockerClient }; diff --git a/utils/extractHostData.js b/utils/extractHostData.js deleted file mode 100644 index 87db239f..00000000 --- a/utils/extractHostData.js +++ /dev/null @@ -1,26 +0,0 @@ -function extractRelevantData(jsonData) { - return { - hostName: jsonData.hostName, - info: { - ID: jsonData.info.ID, - Containers: jsonData.info.Containers, - ContainersRunning: jsonData.info.ContainersRunning, - ContainersPaused: jsonData.info.ContainersPaused, - ContainersStopped: jsonData.info.ContainersStopped, - Images: jsonData.info.Images, - OperatingSystem: jsonData.info.OperatingSystem, - KernelVersion: jsonData.info.KernelVersion, - Architecture: jsonData.info.Architecture, - MemTotal: jsonData.info.MemTotal, - NCPU: jsonData.info.NCPU, - }, - version: { - Components: jsonData.version.Components.reduce((acc, component) => { - acc[component.Name] = component.Version; - return acc; - }, {}), - }, - }; -} - -module.exports = extractRelevantData; diff --git a/utils/notifications/_notify.js b/utils/notifications/_notify.js deleted file mode 100644 index b4a96fda..00000000 --- a/utils/notifications/_notify.js +++ /dev/null @@ -1,59 +0,0 @@ -const logger = require("../../utils/logger"); - -const { telegramNotification } = require("./telegram"); -const { slackNotification } = require("./slack"); -const { discordNotification } = require("./discord"); -const { emailNotification } = require("./email"); -const { whatsappNotification } = require("./whatsapp"); -const { pushbulletNotification } = require("./pushbullet"); -const { pushoverNotification } = require("./pushover"); - -async function notify(type, containerId) { - if (!containerId) { - logger.error("Container ID is required."); - throw new Error("Container ID is required."); - } - - switch (type) { - case "telegram": - logger.debug("Testing Telegram notification..."); - await telegramNotification(containerId); - break; - case "slack": - logger.debug("Testing Slack notification..."); - await slackNotification(containerId); - break; - case "discord": - logger.debug("Testing Discord notification..."); - await discordNotification(containerId); - break; - case "email": - logger.debug("Testing Email notification..."); - await emailNotification(containerId); - break; - case "whatsapp": - logger.debug("Testing WhatsApp notification..."); - await whatsappNotification(containerId); - break; - case "pushbullet": - logger.debug("Testing Pushbullet notification..."); - await pushbulletNotification(containerId); - break; - case "pushover": - logger.debug("Testing Pushover notification..."); - await pushoverNotification(containerId); - break; - default: - const errorMsg = "Unknown notification type."; - logger.error(errorMsg); - throw new Error(errorMsg); - } -} - -if (require.main === module) { - const [type, containerId] = process.argv.slice(2); - notify(type, containerId); - console.log(`Testing ${type}, with: ${containerId}`); -} - -module.exports = notify; diff --git a/utils/notifications/data/template.json b/utils/notifications/data/template.json deleted file mode 100644 index 6a57d442..00000000 --- a/utils/notifications/data/template.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "text": "{{name}} ({{id}}) on {{host}} is {{state}}." -} \ No newline at end of file diff --git a/utils/notifications/discord.js b/utils/notifications/discord.js deleted file mode 100644 index c7bfe828..00000000 --- a/utils/notifications/discord.js +++ /dev/null @@ -1,27 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const discord_webhook_url = process.env.DISCORD_WEBHOOK_URL; - -export async function discordNotification(containerId) { - const discord_message = renderTemplate(containerId); - if (!discord_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch(discord_webhook_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - content: discord_message, - }), - }); - } catch (error) { - logger.error("Error sending Discord message:", error); - } -} diff --git a/utils/notifications/email.js b/utils/notifications/email.js deleted file mode 100644 index d7016795..00000000 --- a/utils/notifications/email.js +++ /dev/null @@ -1,36 +0,0 @@ -import nodemailer from "nodemailer"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const email_sender = process.env.EMAIL_SENDER; -const email_recipient = process.env.EMAIL_RECIPIENT; -const email_password = process.env.EMAIL_PASSWORD; - -export async function emailNotification(containerId) { - const email_message = renderTemplate(containerId); - if (!email_message) { - logger.error("Failed to create notification message."); - return; - } - - const transporter = nodemailer.createTransport({ - service: "gmail", - auth: { - user: email_sender, - pass: email_password, - }, - }); - - const mailOptions = { - from: email_sender, - to: email_recipient, - subject: "Container Notification", - text: email_message, - }; - - try { - await transporter.sendMail(mailOptions); - } catch (error) { - logger.error("Error sending email:", error); - } -} diff --git a/utils/notifications/pushbullet.js b/utils/notifications/pushbullet.js deleted file mode 100644 index 442f44d0..00000000 --- a/utils/notifications/pushbullet.js +++ /dev/null @@ -1,30 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const pushbullet_access_token = process.env.PUSHBULLET_ACCESS_TOKEN; - -export async function pushbulletNotification(containerId) { - const pushbullet_message = renderTemplate(containerId); - if (!pushbullet_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch("https://api.pushbullet.com/v2/pushes", { - method: "POST", - headers: { - "Access-Token": pushbullet_access_token, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - type: "note", - title: "Container Notification", - body: pushbullet_message, - }), - }); - } catch (error) { - logger.error("Error sending Pushbullet message:", error); - } -} diff --git a/utils/notifications/pushover.js b/utils/notifications/pushover.js deleted file mode 100644 index 592e7f09..00000000 --- a/utils/notifications/pushover.js +++ /dev/null @@ -1,30 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const pushover_user_key = process.env.PUSHOVER_USER_KEY; -const pushover_api_token = process.env.PUSHOVER_API_TOKEN; - -export async function pushoverNotification(containerId) { - const pushover_message = renderTemplate(containerId); - if (!pushover_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch("https://api.pushover.net/1/messages.json", { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: new URLSearchParams({ - token: pushover_api_token, - user: pushover_user_key, - message: pushover_message, - }), - }); - } catch (error) { - logger.error("Error sending Pushover message:", error); - } -} diff --git a/utils/notifications/slack.js b/utils/notifications/slack.js deleted file mode 100644 index 2c1a67a2..00000000 --- a/utils/notifications/slack.js +++ /dev/null @@ -1,27 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const slack_webhook_url = process.env.SLACK_WEBHOOK_URL; - -export async function slackNotification(containerId) { - const slack_message = renderTemplate(containerId); - if (!slack_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch(slack_webhook_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - text: slack_message, - }), - }); - } catch (error) { - logger.error("Error sending Slack message:", error); - } -} diff --git a/utils/notifications/telegram.js b/utils/notifications/telegram.js deleted file mode 100644 index 5c79bdc8..00000000 --- a/utils/notifications/telegram.js +++ /dev/null @@ -1,32 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const telegram_bot_token = process.env.TELEGRAM_BOT_TOKEN; -const telegram_chat_id = process.env.TELEGRAM_CHAT_ID; - -export async function telegramNotification(containerId) { - const telegram_message = renderTemplate(containerId); - if (!telegram_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch( - `https://api.telegram.org/bot${telegram_bot_token}/sendMessage`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - chat_id: telegram_chat_id, - text: telegram_message, - }), - }, - ); - } catch (error) { - logger.error("Error sending message:", error); - } -} diff --git a/utils/notifications/whatsapp.js b/utils/notifications/whatsapp.js deleted file mode 100644 index d714b0b6..00000000 --- a/utils/notifications/whatsapp.js +++ /dev/null @@ -1,29 +0,0 @@ -import fetch from "node-fetch"; -import logger from "../logger.js"; -import { renderTemplate } from "./data/template.js"; - -const whatsapp_api_url = process.env.WHATSAPP_API_URL; // e.g., Twilio or other API service -const whatsapp_recipient = process.env.WHATSAPP_RECIPIENT; - -export async function whatsappNotification(containerId) { - const whatsapp_message = renderTemplate(containerId); - if (!whatsapp_message) { - logger.error("Failed to create notification message."); - return; - } - - try { - await fetch(whatsapp_api_url, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - to: whatsapp_recipient, - body: whatsapp_message, - }), - }); - } catch (error) { - logger.error("Error sending WhatsApp message:", error); - } -} diff --git a/utils/writeOfflineLog.js b/utils/writeOfflineLog.js deleted file mode 100644 index 4d26b1d5..00000000 --- a/utils/writeOfflineLog.js +++ /dev/null @@ -1,31 +0,0 @@ -const fs = require("fs"); -const path = require("path"); -const logger = require("../utils/logger"); - -const LOG_FILE_PATH = path.join(__dirname, "../logs/hostStats.json"); - -function writeOfflineLog(message) { - try { - if (!fs.existsSync(LOG_FILE_PATH)) { - fs.writeFileSync(LOG_FILE_PATH, message); - } - } catch (error) { - logger.error("Error writing one time reference log: ", error); - } -} - -function readOfflineLog() { - fs.readFile(LOG_FILE_PATH, "utf-8", (err, data) => { - if (err) { - logger.error("Error reading offline log:", err); - } - - logger.debug("Returning data:", data); - return data; - }); -} - -module.exports = { - writeOfflineLog, - readOfflineLog, -}; diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 00000000..1418f00e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3298 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@apidevtools/json-schema-ref-parser@^9.0.6": + version "9.1.2" + resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz" + integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@apidevtools/openapi-schemas@^2.0.4": + version "2.1.0" + resolved "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@10.0.3": + version "10.0.3" + resolved "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz" + integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.6" + "@apidevtools/openapi-schemas" "^2.0.4" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + z-schema "^5.0.1" + +"@babel/generator@7.18.2": + version "7.18.2" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz" + integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== + dependencies: + "@babel/types" "^7.18.2" + "@jridgewell/gen-mapping" "^0.3.0" + jsesc "^2.5.1" + +"@babel/helper-string-parser@^7.18.10": + 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== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.25.9" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" + integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== + +"@babel/parser@7.18.4": + version "7.18.4" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz" + integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== + +"@babel/types@^7.18.2", "@babel/types@7.19.0": + version "7.19.0" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" + integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@balena/dockerignore@^1.0.2": + version "1.0.2" + resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz" + integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== + +"@colors/colors@^1.6.0", "@colors/colors@1.6.0": + version "1.6.0" + resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz" + integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== + +"@cspotcode/source-map-support@^0.8.0": + 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== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + +"@dabh/diagnostics@^2.0.2": + version "2.0.3" + resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz" + integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + +"@esbuild/linux-x64@0.23.1": + version "0.23.1" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz" + integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== + +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@jridgewell/gen-mapping@^0.3.0": + version "0.3.5" + resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.24": + version "0.3.25" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + +"@mapbox/node-pre-gyp@^1.0.11": + version "1.0.11" + resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz" + integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== + dependencies: + detect-libc "^2.0.0" + https-proxy-agent "^5.0.0" + make-dir "^3.1.0" + node-fetch "^2.6.7" + nopt "^5.0.0" + npmlog "^5.0.1" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.11" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@playwright/test@^1.49.0": + version "1.49.0" + resolved "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz" + integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== + dependencies: + playwright "1.49.0" + +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@tsconfig/node10@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" + integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + +"@types/bcrypt@^5.0.2": + version "5.0.2" + resolved "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz" + integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== + dependencies: + "@types/node" "*" + +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.17": + version "2.8.17" + resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + +"@types/docker-modem@*": + version "3.0.6" + resolved "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz" + integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg== + dependencies: + "@types/node" "*" + "@types/ssh2" "*" + +"@types/dockerode@^3.3.31": + version "3.3.32" + resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz" + integrity sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg== + dependencies: + "@types/docker-modem" "*" + "@types/node" "*" + "@types/ssh2" "*" + +"@types/express-handlebars@^5.3.1": + version "5.3.1" + resolved "https://registry.npmjs.org/@types/express-handlebars/-/express-handlebars-5.3.1.tgz" + integrity sha512-DSzaERLO4gHb8AqnrL58jzSDyT0yDdl6HqDc+bGz1Hf0nrG1FK30nHGzv8NBEGR8QV9eUGB/YaE0Qj3NjF7siw== + +"@types/express-serve-static-core@^5.0.0": + version "5.0.2" + resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz" + integrity sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@*", "@types/express@^5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz" + integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + +"@types/json-schema@^7.0.6": + version "7.0.15" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + +"@types/node-fetch@^2.6.12": + version "2.6.12" + resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz" + integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== + dependencies: + "@types/node" "*" + form-data "^4.0.0" + +"@types/node@*", "@types/node@^22.9.0": + version "22.10.1" + resolved "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz" + integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== + dependencies: + undici-types "~6.20.0" + +"@types/node@^18.11.18": + version "18.19.67" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz" + integrity sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ== + dependencies: + undici-types "~5.26.4" + +"@types/nodemailer@^6.4.17": + version "6.4.17" + resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz" + integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww== + dependencies: + "@types/node" "*" + +"@types/qs@*": + version "6.9.17" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz" + integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "0.17.4" + resolved "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/ssh2@*": + version "1.15.1" + resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz" + integrity sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA== + dependencies: + "@types/node" "^18.11.18" + +"@types/supports-color@^8.1.3": + version "8.1.3" + resolved "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz" + integrity sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg== + +"@types/swagger-jsdoc@^6.0.4": + version "6.0.4" + resolved "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz" + integrity sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ== + +"@types/swagger-ui-express@^4.1.7": + version "4.1.7" + resolved "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz" + integrity sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g== + dependencies: + "@types/express" "*" + "@types/serve-static" "*" + +"@types/triple-beam@^1.3.2": + version "1.3.5" + resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz" + integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== + +abbrev@1: + version "1.1.1" + resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +accepts@~1.3.8: + 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== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-jsx-walk@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz" + integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA== + +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-loose@^8.4.0: + version "8.4.0" + resolved "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.4.0.tgz" + integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== + dependencies: + acorn "^8.11.0" + +acorn-walk@^8.1.1, acorn-walk@^8.3.4: + version "8.3.4" + resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + dependencies: + acorn "^8.11.0" + +"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: + version "8.14.0" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" + integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== + +agent-base@^6.0.2, agent-base@6: + version "6.0.2" + resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +agentkeepalive@^4.1.3: + version "4.5.0" + resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" + integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== + dependencies: + humanize-ms "^1.2.1" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +are-we-there-yet@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz" + integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +asn1@^0.2.6: + version "0.2.6" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + +async@^3.2.3: + version "3.2.6" + resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + +bcrypt@^5.1.1: + version "5.1.1" + resolved "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz" + integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== + dependencies: + "@mapbox/node-pre-gyp" "^1.0.11" + node-addon-api "^5.0.0" + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.13.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +buildcheck@~0.0.6: + version "0.0.6" + resolved "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz" + integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +cacache@^15.2.0: + version "15.3.0" + resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +call-bind-apply-helpers@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz" + integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + +call-bind@^1.0.7: + version "1.0.8" + resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" + integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-define-property "^1.0.0" + get-intrinsic "^1.2.4" + set-function-length "^1.2.2" + +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^5.3.0: + version "5.3.0" + resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" + integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== + +chokidar@^3.5.2: + version "3.6.0" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.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" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz" + integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA== + dependencies: + readdirp "^4.0.1" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + dependencies: + restore-cursor "^5.0.0" + +cli-spinners@^2.9.2: + version "2.9.2" + resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" + integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^1.9.3: + version "1.9.3" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@^1.0.0, color-name@1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.6.0: + version "1.9.1" + resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + +color-support@^1.1.2, color-support@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +color@^3.1.3: + version "3.2.1" + resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz" + integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== + dependencies: + color-convert "^1.9.3" + color-string "^1.6.0" + +colorspace@1.1.x: + version "1.1.4" + resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz" + integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== + dependencies: + color "^3.1.3" + text-hex "1.0.x" + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +commander@^12.1.0: + version "12.1.0" + resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== + +commander@^9.4.1: + version "9.5.0" + resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz" + integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== + +commander@6.2.0: + version "6.2.0" + resolved "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + +cpu-features@~0.0.10: + version "0.0.10" + resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz" + integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== + dependencies: + buildcheck "~0.0.6" + nan "^2.19.0" + +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + +data-uri-to-buffer@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" + integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== + +debug@^4, debug@^4.1.1, debug@^4.3.3, debug@4: + version "4.4.0" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +dependency-cruiser@^16.5.0: + version "16.7.0" + resolved "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.7.0.tgz" + integrity sha512-522LLjHINl9r0RIZ8/6s6TqIHTuEJG3XDU2WPSm9dG0rvLUYVyQwE9ID31tDFs4OOyEhdOPaqAaAG1jRv/Zwbg== + dependencies: + acorn "^8.14.0" + acorn-jsx "^5.3.2" + acorn-jsx-walk "^2.0.0" + acorn-loose "^8.4.0" + acorn-walk "^8.3.4" + ajv "^8.17.1" + commander "^12.1.0" + enhanced-resolve "^5.17.1" + ignore "^6.0.2" + interpret "^3.1.1" + is-installed-globally "^1.0.0" + json5 "^2.2.3" + memoize "^10.0.0" + picocolors "^1.1.1" + picomatch "^4.0.2" + prompts "^2.4.2" + rechoir "^0.8.0" + safe-regex "^2.1.1" + semver "^7.6.3" + teamcity-service-messages "^0.1.14" + tsconfig-paths-webpack-plugin "^4.2.0" + watskeburt "^4.1.1" + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detect-libc@^2.0.0: + version "2.0.3" + resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" + integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== + +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +docker-modem@^5.0.3: + version "5.0.3" + resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz" + integrity sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^1.15.0" + +dockerode@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz" + integrity sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w== + dependencies: + "@balena/dockerignore" "^1.0.2" + docker-modem "^5.0.3" + tar-fs "~2.0.1" + +doctrine@3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dunder-proto@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz" + integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== + dependencies: + call-bind-apply-helpers "^1.0.0" + es-errors "^1.3.0" + gopd "^1.2.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +emoji-regex@^10.3.0: + version "10.4.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + +encoding@^0.1.0, encoding@^0.1.12: + version "0.1.13" + resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + +end-of-stream@^1.1.0, end-of-stream@^1.4.1: + 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== + dependencies: + once "^1.4.0" + +enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: + version "5.17.1" + resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + +err-code@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" + integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== + +es-define-property@^1.0.0, es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + +esbuild@~0.23.0: + version "0.23.1" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz" + integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== + optionalDependencies: + "@esbuild/aix-ppc64" "0.23.1" + "@esbuild/android-arm" "0.23.1" + "@esbuild/android-arm64" "0.23.1" + "@esbuild/android-x64" "0.23.1" + "@esbuild/darwin-arm64" "0.23.1" + "@esbuild/darwin-x64" "0.23.1" + "@esbuild/freebsd-arm64" "0.23.1" + "@esbuild/freebsd-x64" "0.23.1" + "@esbuild/linux-arm" "0.23.1" + "@esbuild/linux-arm64" "0.23.1" + "@esbuild/linux-ia32" "0.23.1" + "@esbuild/linux-loong64" "0.23.1" + "@esbuild/linux-mips64el" "0.23.1" + "@esbuild/linux-ppc64" "0.23.1" + "@esbuild/linux-riscv64" "0.23.1" + "@esbuild/linux-s390x" "0.23.1" + "@esbuild/linux-x64" "0.23.1" + "@esbuild/netbsd-x64" "0.23.1" + "@esbuild/openbsd-arm64" "0.23.1" + "@esbuild/openbsd-x64" "0.23.1" + "@esbuild/sunos-x64" "0.23.1" + "@esbuild/win32-arm64" "0.23.1" + "@esbuild/win32-ia32" "0.23.1" + "@esbuild/win32-x64" "0.23.1" + +escalade@^3.1.1: + version "3.2.0" + resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +expand-template@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" + integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== + +express-rate-limit@^7.4.1: + version "7.4.1" + resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz" + integrity sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg== + +express@^4.21.1, "express@>=4.0.0 || >=5.0.0-beta", "express@4 || 5 || ^5.0.0-beta.1": + version "4.21.2" + resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.3" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.7.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~2.0.0" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.3.1" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.3" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.12" + proxy-addr "~2.0.7" + qs "6.13.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.19.0" + serve-static "1.16.2" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +fast-deep-equal@^3.1.3: + 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-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@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" + +fast-uri@^3.0.1: + version "3.0.3" + resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== + +fastq@^1.6.0: + version "1.17.1" + resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" + integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== + dependencies: + reusify "^1.0.4" + +fecha@^4.2.0: + version "4.2.3" + resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz" + integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== + +fetch-blob@^3.1.2, fetch-blob@^3.1.4: + version "3.2.0" + resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz" + integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== + dependencies: + node-domexception "^1.0.0" + web-streams-polyfill "^3.0.3" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== + dependencies: + debug "2.6.9" + encodeurl "~2.0.0" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +formdata-polyfill@^4.0.10: + version "4.0.10" + resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz" + integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== + dependencies: + fetch-blob "^3.1.2" + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +from2@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" + integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + +fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gauge@^4.0.3: + version "4.0.4" + resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz" + integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.3" + console-control-strings "^1.1.0" + has-unicode "^2.0.1" + signal-exit "^3.0.7" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.5" + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-east-asian-width@^1.0.0: + version "1.3.0" + resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz" + integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== + +get-intrinsic@^1.2.4: + version "1.2.5" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz" + integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== + dependencies: + call-bind-apply-helpers "^1.0.0" + dunder-proto "^1.0.0" + es-define-property "^1.0.1" + es-errors "^1.3.0" + function-bind "^1.1.2" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + +get-tsconfig@^4.7.5: + version "4.8.1" + resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz" + integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== + dependencies: + resolve-pkg-maps "^1.0.0" + +github-from-package@0.0.0: + version "0.0.0" + resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" + integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@^7.1.3, glob@^7.1.4: + version "7.2.3" + resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + 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" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global-directory@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz" + integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== + dependencies: + ini "4.1.1" + +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + 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" + +gopd@^1.0.1, gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== + +graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: + version "4.2.11" + resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has@^1.0.3: + version "1.0.4" + resolved "https://registry.npmjs.org/has/-/has-1.0.4.tgz" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +http-cache-semantics@^4.1.0: + version "4.1.1" + resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== + dependencies: + "@tootallnate/once" "1" + agent-base "6" + debug "4" + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +https@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/https/-/https-1.0.0.tgz" + integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg== + +humanize-ms@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" + integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== + dependencies: + ms "^2.0.0" + +iconv-lite@^0.6.2: + version "0.6.3" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +ignore-by-default@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" + integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== + +ignore@^5.2.0: + version "5.3.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + +ignore@^6.0.2: + version "6.0.2" + resolved "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz" + integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4: + version "2.0.4" + resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + +ini@4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz" + integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== + +interpret@^3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" + integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== + +into-stream@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz" + integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== + dependencies: + from2 "^2.3.0" + p-is-promise "^3.0.0" + +ip-address@^9.0.5: + version "9.0.5" + resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz" + integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== + dependencies: + jsbn "1.1.0" + sprintf-js "^1.1.3" + +ipaddr.js@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== + +ipaddr.js@1.9.1: + 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-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.15.1" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== + dependencies: + hasown "^2.0.2" + +is-core-module@2.9.0: + version "2.9.0" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-fullwidth-code-point@^3.0.0: + 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-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-installed-globally@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz" + integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== + dependencies: + global-directory "^4.0.1" + is-path-inside "^4.0.0" + +is-interactive@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" + integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== + +is-lambda@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" + integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-path-inside@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz" + integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-unicode-supported@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" + integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== + +is-unicode-supported@^2.0.0: + version "2.1.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz" + integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsbn@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" + integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json5@^2.2.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== + +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.mergewith@^4.6.2: + version "4.6.2" + resolved "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + +log-symbols@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz" + integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== + dependencies: + chalk "^5.3.0" + is-unicode-supported "^1.3.0" + +logform@^2.7.0: + version "2.7.0" + resolved "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz" + integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== + dependencies: + "@colors/colors" "1.6.0" + "@types/triple-beam" "^1.3.2" + fecha "^4.2.0" + ms "^2.1.1" + safe-stable-stringify "^2.3.1" + triple-beam "^1.3.0" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +make-dir@^3.1.0: + 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== + dependencies: + semver "^6.0.0" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +make-fetch-happen@^9.1.0: + version "9.1.0" + resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz" + integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== + dependencies: + agentkeepalive "^4.1.3" + cacache "^15.2.0" + http-cache-semantics "^4.1.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + is-lambda "^1.0.1" + lru-cache "^6.0.0" + minipass "^3.1.3" + minipass-collect "^1.0.2" + minipass-fetch "^1.3.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.2" + promise-retry "^2.0.1" + socks-proxy-agent "^6.0.0" + ssri "^8.0.0" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +memoize@^10.0.0: + version "10.0.0" + resolved "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz" + integrity sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA== + dependencies: + mimic-function "^5.0.0" + +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== + +merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +micromatch@^4.0.4: + version "4.0.8" + resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== + dependencies: + braces "^3.0.3" + picomatch "^2.3.1" + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + +minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: + version "1.2.8" + resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-fetch@^1.3.2: + version "1.4.1" + resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz" + integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== + dependencies: + minipass "^3.1.0" + minipass-sized "^1.0.3" + minizlib "^2.0.0" + optionalDependencies: + encoding "^0.1.12" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: + version "1.2.4" + resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass-sized@^1.0.3: + version "1.0.3" + resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz" + integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.3.6" + resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" + integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== + dependencies: + yallist "^4.0.0" + +minipass@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" + integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + +minizlib@^2.0.0, minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: + version "0.5.3" + resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +ms@^2.0.0, ms@^2.1.1, ms@^2.1.3, ms@2.1.3: + version "2.1.3" + resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +multistream@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz" + integrity sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw== + dependencies: + once "^1.4.0" + readable-stream "^3.6.0" + +nan@^2.19.0, nan@^2.20.0: + version "2.22.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz" + integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== + +napi-build-utils@^1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" + integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== + +negotiator@^0.6.2, negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +node-abi@^3.3.0: + version "3.71.0" + resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz" + integrity sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw== + dependencies: + semver "^7.3.5" + +node-addon-api@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" + integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +node-domexception@^1.0.0: + 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@^2.6.6: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + +node-fetch@^3.3.2: + version "3.3.2" + resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz" + integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== + dependencies: + data-uri-to-buffer "^4.0.0" + fetch-blob "^3.1.4" + formdata-polyfill "^4.0.10" + +node-gyp@8.x: + version "8.4.1" + resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz" + integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== + dependencies: + env-paths "^2.2.0" + glob "^7.1.4" + graceful-fs "^4.2.6" + make-fetch-happen "^9.1.0" + nopt "^5.0.0" + npmlog "^6.0.0" + rimraf "^3.0.2" + semver "^7.3.5" + tar "^6.1.2" + which "^2.0.2" + +nodemailer@^6.9.16: + version "6.9.16" + resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz" + integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== + +nodemon@^3.1.7: + version "3.1.7" + resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz" + integrity sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ== + dependencies: + 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" + +nopt@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" + integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== + dependencies: + abbrev "1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +npmlog@^6.0.0: + version "6.0.2" + resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz" + integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== + dependencies: + are-we-there-yet "^3.0.0" + console-control-strings "^1.1.0" + gauge "^4.0.3" + set-blocking "^2.0.0" + +object-assign@^4, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-inspect@^1.13.1: + version "1.13.3" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz" + integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" + +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + +openapi-types@>=7: + version "12.1.3" + resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz" + integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== + +ora@^8.1.1: + version "8.1.1" + resolved "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz" + integrity sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw== + dependencies: + chalk "^5.3.0" + cli-cursor "^5.0.0" + cli-spinners "^2.9.2" + is-interactive "^2.0.0" + is-unicode-supported "^2.0.0" + log-symbols "^6.0.0" + stdin-discarder "^0.2.2" + string-width "^7.2.0" + strip-ansi "^7.1.0" + +p-is-promise@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz" + integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.12: + version "0.1.12" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^2.0.4: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + +pkg-fetch@3.4.2: + version "3.4.2" + resolved "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz" + integrity sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA== + dependencies: + chalk "^4.1.2" + fs-extra "^9.1.0" + https-proxy-agent "^5.0.0" + node-fetch "^2.6.6" + progress "^2.0.3" + semver "^7.3.5" + tar-fs "^2.1.1" + yargs "^16.2.0" + +pkg@^5.8.1: + version "5.8.1" + resolved "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz" + integrity sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA== + dependencies: + "@babel/generator" "7.18.2" + "@babel/parser" "7.18.4" + "@babel/types" "7.19.0" + chalk "^4.1.2" + fs-extra "^9.1.0" + globby "^11.1.0" + into-stream "^6.0.0" + is-core-module "2.9.0" + minimist "^1.2.6" + multistream "^4.1.0" + pkg-fetch "3.4.2" + prebuild-install "7.1.1" + resolve "^1.22.0" + stream-meter "^1.0.4" + +playwright-core@1.49.0: + version "1.49.0" + resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz" + integrity sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA== + +playwright@1.49.0: + version "1.49.0" + resolved "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz" + integrity sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A== + dependencies: + playwright-core "1.49.0" + optionalDependencies: + fsevents "2.3.2" + +prebuild-install@^7.1.1: + version "7.1.2" + resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz" + integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +prebuild-install@7.1.1: + version "7.1.1" + resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== + dependencies: + detect-libc "^2.0.0" + expand-template "^2.0.3" + github-from-package "0.0.0" + minimist "^1.2.3" + mkdirp-classic "^0.5.3" + napi-build-utils "^1.0.1" + node-abi "^3.3.0" + pump "^3.0.0" + rc "^1.2.7" + simple-get "^4.0.0" + tar-fs "^2.0.0" + tunnel-agent "^0.6.0" + +process-nextick-args@~2.0.0: + 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== + +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise-retry@^2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz" + integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== + dependencies: + err-code "^2.0.2" + retry "^0.12.0" + +prompts@^2.4.2: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +proxy-addr@~2.0.7: + 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== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +pstree.remy@^1.1.8: + version "1.1.8" + resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" + integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== + +pump@^3.0.0: + version "3.0.2" + resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz" + integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +qs@6.13.0: + version "6.13.0" + resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +range-parser@~1.2.1: + 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@2.5.2: + version "2.5.2" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.0: + version "2.3.8" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + 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" + +readable-stream@^2.1.4: + version "2.3.8" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" + integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== + dependencies: + 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" + +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^4.0.1: + version "4.0.2" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz" + integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.8.0: + version "0.8.0" + resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" + integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== + dependencies: + resolve "^1.20.0" + +regexp-tree@~0.1.1: + version "0.1.27" + resolved "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + 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-pkg-maps@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" + integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== + +resolve@^1.20.0, resolve@^1.22.0: + version "1.22.8" + resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + dependencies: + onetime "^7.0.0" + signal-exit "^4.1.0" + +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +safe-buffer@^5.0.1, safe-buffer@~5.2.0, safe-buffer@5.2.1: + version "5.2.1" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz" + integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== + dependencies: + regexp-tree "~0.1.1" + +safe-stable-stringify@^2.3.1: + version "2.5.0" + resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" + integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: + version "2.1.2" + resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@^6.0.0: + version "6.3.1" + resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +semver@^7.3.5, semver@^7.5.3, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + +send@0.19.0: + version "0.19.0" + resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-function-length@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + +signal-exit@^3.0.0, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + +simple-concat@^1.0.0: + version "1.0.1" + resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== + +simple-get@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" + integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== + dependencies: + decompress-response "^6.0.0" + once "^1.3.1" + simple-concat "^1.0.0" + +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" + integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== + dependencies: + is-arrayish "^0.3.1" + +simple-update-notifier@^2.0.0: + 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== + dependencies: + semver "^7.5.3" + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks-proxy-agent@^6.0.0: + version "6.2.1" + resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz" + integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== + dependencies: + agent-base "^6.0.2" + debug "^4.3.3" + socks "^2.6.2" + +socks@^2.6.2: + version "2.8.3" + resolved "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz" + integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== + dependencies: + ip-address "^9.0.5" + smart-buffer "^4.2.0" + +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz" + integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== + +sprintf-js@^1.1.3: + version "1.1.3" + resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + +sqlite3@^5.1.7: + version "5.1.7" + resolved "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz" + integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== + dependencies: + bindings "^1.5.0" + node-addon-api "^7.0.0" + prebuild-install "^7.1.1" + tar "^6.1.11" + optionalDependencies: + node-gyp "8.x" + +ssh2@^1.15.0: + version "1.16.0" + resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz" + integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.10" + nan "^2.20.0" + +ssri@^8.0.0, ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stack-trace@0.0.x: + version "0.0.10" + resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" + integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== + +statuses@2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +stdin-discarder@^0.2.2: + version "0.2.2" + resolved "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz" + integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== + +stream-meter@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz" + integrity sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ== + dependencies: + readable-stream "^2.1.4" + +string_decoder@^1.1.1: + 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== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^7.2.0: + version "7.2.0" + resolved "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== + dependencies: + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + +supports-color@^5.5.0: + version "5.5.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + 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-jsdoc@^6.2.8: + version "6.2.8" + resolved "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz" + integrity sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ== + dependencies: + commander "6.2.0" + doctrine "3.0.0" + glob "7.1.6" + lodash.mergewith "^4.6.2" + swagger-parser "^10.0.3" + yaml "2.0.0-1" + +swagger-parser@^10.0.3: + version "10.0.3" + resolved "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz" + integrity sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg== + dependencies: + "@apidevtools/swagger-parser" "10.0.3" + +swagger-ui-dist@>=5.0.0: + version "5.18.2" + resolved "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz" + integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== + dependencies: + "@scarf/scarf" "=1.4.0" + +swagger-ui-express@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz" + integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== + dependencies: + swagger-ui-dist ">=5.0.0" + +tapable@^2.2.0, tapable@^2.2.1: + version "2.2.1" + resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar-fs@^2.0.0, tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-fs@^2.1.1: + version "2.1.1" + resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" + integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.1.4" + +tar-stream@^2.0.0, tar-stream@^2.1.4: + version "2.2.0" + resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + +tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: + version "6.2.1" + resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + 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" + +teamcity-service-messages@^0.1.14: + version "0.1.14" + resolved "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz" + integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w== + +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + 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== + dependencies: + is-number "^7.0.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +touch@^3.1.0: + version "3.1.1" + resolved "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz" + integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +triple-beam@^1.3.0: + version "1.4.1" + resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz" + integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== + +ts-node@^10.9.2: + version "10.9.2" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@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" + +tsconfig-paths-webpack-plugin@^4.2.0: + version "4.2.0" + resolved "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz" + integrity sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.7.0" + tapable "^2.2.1" + tsconfig-paths "^4.1.2" + +tsconfig-paths@^4.1.2: + version "4.2.0" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" + integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== + dependencies: + json5 "^2.2.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tsx@^4.19.2: + version "4.19.2" + resolved "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz" + integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== + dependencies: + esbuild "~0.23.0" + get-tsconfig "^4.7.5" + optionalDependencies: + fsevents "~2.3.3" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" + integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typescript@>=2.7: + version "5.7.2" + resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== + +uglify-js@^3.19.3: + version "3.19.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" + integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== + +undefsafe@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" + integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +universalify@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" + integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== + +unpipe@~1.0.0, unpipe@1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +util-deprecate@^1.0.1, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +v8-compile-cache-lib@^3.0.1: + 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== + +validator@^13.7.0: + version "13.12.0" + resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + +vary@^1, vary@~1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +watskeburt@^4.1.1: + version "4.2.2" + resolved "https://registry.npmjs.org/watskeburt/-/watskeburt-4.2.2.tgz" + integrity sha512-AOCg1UYxWpiHW1tUwqpJau8vzarZYTtzl2uu99UptBmbzx6kOzCGMfRLF6KIRX4PYekmryn89MzxlRNkL66YyA== + +web-streams-polyfill@^3.0.3: + version "3.3.3" + resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz" + integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2, wide-align@^1.1.5: + version "1.1.5" + resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +winston-transport@^4.9.0: + version "4.9.0" + resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz" + integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== + dependencies: + logform "^2.7.0" + readable-stream "^3.6.2" + triple-beam "^1.3.0" + +winston@^3.15.0: + version "3.17.0" + resolved "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz" + integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== + dependencies: + "@colors/colors" "^1.6.0" + "@dabh/diagnostics" "^2.0.2" + async "^3.2.3" + is-stream "^2.0.0" + logform "^2.7.0" + one-time "^1.0.0" + readable-stream "^3.4.0" + safe-stable-stringify "^2.3.1" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.9.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +yaml@2.0.0-1: + version "2.0.0-1" + resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + 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" + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + +z-schema@^5.0.1: + version "5.0.5" + resolved "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz" + integrity sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^9.4.1" From 84c3e993a69d6accfc3b7d31966874cf6b792829 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 20 Dec 2024 22:06:47 +0100 Subject: [PATCH 018/135] Fix: Disabled image pushing for PRs running against dev branch --- .github/workflows/test-build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-build.yaml b/.github/workflows/test-build.yaml index fb471834..8c805d46 100644 --- a/.github/workflows/test-build.yaml +++ b/.github/workflows/test-build.yaml @@ -52,7 +52,7 @@ jobs: uses: docker/build-push-action@v5 with: platforms: linux/amd64,linux/arm64 - push: true + push: false tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} cache-from: type=gha From 49b69867db068ee38a63f6152bae6b81e05b45bb Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 10:51:48 +0100 Subject: [PATCH 019/135] Fix: Dropping linux/arm/v7 due to workflow timeout (sorry guys) --- .github/workflows/build-dev.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index afc8ffc1..09f9b0e9 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -39,7 +39,7 @@ jobs: - name: Build and push uses: docker/build-push-action@v5 with: - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: linux/amd64,linux/arm64, push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} From a2618b8ea170c4ad2ec2c9a7af9ab327d3a88056 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 12:03:24 +0100 Subject: [PATCH 020/135] Chore: Add exiting log --- package-lock.json | 898 ---------------------- package.json | 1 - src/init.ts | 29 + src/misc/dependencyGraphs/mermaid-all.txt | 197 ++--- src/utils/removeUnusedDeps.sh | 2 +- yarn.lock | 470 +---------- 6 files changed, 150 insertions(+), 1447 deletions(-) diff --git a/package-lock.json b/package-lock.json index 641c0d3a..dcd2ac0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,7 +40,6 @@ "dependency-cruiser": "^16.5.0", "nodemon": "^3.1.7", "ora": "^8.1.1", - "pkg": "^5.8.1", "ts-node": "^10.9.2", "tsx": "^4.19.2", "uglify-js": "^3.19.3" @@ -93,69 +92,6 @@ "openapi-types": ">=7" } }, - "node_modules/@babel/generator": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", - "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.2", - "@jridgewell/gen-mapping": "^0.3.0", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@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, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", - "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@balena/dockerignore": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", @@ -610,32 +546,6 @@ "license": "MIT", "optional": true }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping/node_modules/@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, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -646,16 +556,6 @@ "node": ">=6.0.0" } }, - "node_modules/@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, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -720,44 +620,6 @@ } } }, - "node_modules/@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, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@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, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@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, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -1311,16 +1173,6 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1343,16 +1195,6 @@ "dev": true, "license": "MIT" }, - "node_modules/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, - "license": "ISC", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1682,18 +1524,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -1819,13 +1649,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/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, - "license": "MIT" - }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -2023,19 +1846,6 @@ "node": ">=0.3.1" } }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/docker-modem": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", @@ -2239,16 +2049,6 @@ "@esbuild/win32-x64": "0.23.1" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2365,23 +2165,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@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" - }, - "engines": { - "node": ">=8.6.0" - } - }, "node_modules/fast-uri": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", @@ -2389,16 +2172,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -2531,72 +2304,12 @@ "node": ">= 0.6" } }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/from2/node_modules/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, - "license": "MIT", - "dependencies": { - "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" - } - }, - "node_modules/from2/node_modules/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, - "license": "MIT" - }, - "node_modules/from2/node_modules/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, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -2660,16 +2373,6 @@ "node": ">=10" } }, - "node_modules/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, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, "node_modules/get-east-asian-width": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", @@ -2774,37 +2477,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "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" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2824,16 +2496,6 @@ "devOptional": true, "license": "ISC" }, - "node_modules/has": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", - "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3066,23 +2728,6 @@ "node": ">=10.13.0" } }, - "node_modules/into-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", - "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "from2": "^2.3.0", - "p-is-promise": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -3258,13 +2903,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3291,19 +2929,6 @@ "license": "MIT", "optional": true }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3324,19 +2949,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -3524,16 +3136,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3543,33 +3145,6 @@ "node": ">= 0.6" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3768,31 +3343,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/multistream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", - "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "once": "^1.4.0", - "readable-stream": "^3.6.0" - } - }, "node_modules/nan": { "version": "2.22.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", @@ -4227,16 +3777,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -4284,16 +3824,6 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4314,221 +3844,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz", - "integrity": "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/generator": "7.18.2", - "@babel/parser": "7.18.4", - "@babel/types": "7.19.0", - "chalk": "^4.1.2", - "fs-extra": "^9.1.0", - "globby": "^11.1.0", - "into-stream": "^6.0.0", - "is-core-module": "2.9.0", - "minimist": "^1.2.6", - "multistream": "^4.1.0", - "pkg-fetch": "3.4.2", - "prebuild-install": "7.1.1", - "resolve": "^1.22.0", - "stream-meter": "^1.0.4" - }, - "bin": { - "pkg": "lib-es5/bin.js" - }, - "peerDependencies": { - "node-notifier": ">=9.0.1" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/pkg-fetch": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", - "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "fs-extra": "^9.1.0", - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.6", - "progress": "^2.0.3", - "semver": "^7.3.5", - "tar-fs": "^2.1.1", - "yargs": "^16.2.0" - }, - "bin": { - "pkg-fetch": "lib-es5/bin.js" - } - }, - "node_modules/pkg-fetch/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/pkg-fetch/node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true, - "license": "ISC" - }, - "node_modules/pkg-fetch/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-fetch/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/pkg-fetch/node_modules/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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-fetch/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "license": "MIT", - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/pkg/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/pkg/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg/node_modules/is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/pkg/node_modules/prebuild-install": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", - "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.0", - "expand-template": "^2.0.3", - "github-from-package": "0.0.0", - "minimist": "^1.2.3", - "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", - "node-abi": "^3.3.0", - "pump": "^3.0.0", - "rc": "^1.2.7", - "simple-get": "^4.0.0", - "tar-fs": "^2.0.0", - "tunnel-agent": "^0.6.0" - }, - "bin": { - "prebuild-install": "bin.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/pkg/node_modules/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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/playwright": { "version": "1.49.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", @@ -4587,23 +3902,6 @@ "node": ">=10" } }, - "node_modules/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, - "license": "MIT" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -4693,27 +3991,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/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, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4809,16 +4086,6 @@ "regexp-tree": "bin/regexp-tree" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -4897,17 +4164,6 @@ "node": ">= 4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4924,30 +4180,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/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, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5195,16 +4427,6 @@ "dev": true, "license": "MIT" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -5350,49 +4572,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stream-meter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", - "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "^2.1.4" - } - }, - "node_modules/stream-meter/node_modules/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, - "license": "MIT", - "dependencies": { - "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" - } - }, - "node_modules/stream-meter/node_modules/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, - "license": "MIT" - }, - "node_modules/stream-meter/node_modules/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, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -5642,16 +4821,6 @@ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5941,16 +5110,6 @@ "imurmurhash": "^0.1.4" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -6099,40 +5258,12 @@ "node": ">= 12.0.0" } }, - "node_modules/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, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -6148,35 +5279,6 @@ "node": ">= 6" } }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "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" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/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, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 78ac9460..5e85eced 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "dependency-cruiser": "^16.5.0", "nodemon": "^3.1.7", "ora": "^8.1.1", - "pkg": "^5.8.1", "ts-node": "^10.9.2", "tsx": "^4.19.2", "uglify-js": "^3.19.3" diff --git a/src/init.ts b/src/init.ts index 3979eb6f..6d3854a0 100644 --- a/src/init.ts +++ b/src/init.ts @@ -1,4 +1,5 @@ import express, { Request, Response, NextFunction } from "express"; +import process from "node:process"; import swaggerDocs from "./utils/swaggerDocs"; import auth from "./routes/auth/routes"; import data from "./routes/data/routes"; @@ -13,6 +14,7 @@ import { limiter } from "./middleware/rateLimiter"; import { scheduleFetch } from "./controllers/scheduler"; import cors from "cors"; import { blockWhileLocked } from "./middleware/checkLock"; +import logger from "./utils/logger"; const initializeApp = (app: express.Application): void => { app.use(cors()); @@ -42,6 +44,33 @@ const initializeApp = (app: express.Application): void => { app.get("/", (req: Request, res: Response) => { res.redirect("/api-docs"); }); + + process.on("exit", (code: number) => { + logger.warn(`Server exiting (Code: ${code})`); + console.log(` + \u001b[1;31mThank you for using\u001b[0m + + \u001b[1;34m###### ###### #### ### ### #### ######### ###### #########\u001b[0m + \u001b[1;34m### ### ### ### ### ### ### ### ### ### ### ###\u001b[0m + \u001b[1;34m### ### ### ### ### ###### #### ### ### ### ###\u001b[0m + \u001b[1;34m### ### ### ### ### ### ### #### ### ############ ###\u001b[0m + \u001b[1;34m### ### ### ### ### ### ### #### ### ### ### ###\u001b[0m + \u001b[1;34m###### ###### #### ### ### #### ### ### ### ### \u001b[0m(\u001b[1;33mAPI - v2.0.0\u001b[0m) + + \u001b[1;36mUseful links before you go:\u001b[0m + + - Documentation: \u001b[1;32mhttps://outline.itsnik.de/s/dockstat\u001b[0m + - GitHub (Frontend): \u001b[1;32mhttps://github.com/its4nik/dockstat\u001b[0m + - GitHub (Backend): \u001b[1;32mhttps://github.com/its4nik/dockstatapi\u001b[0m + - API Documentation: \u001b[1;32mhttp://localhost:7000/api-docs\u001b[0m + + \u001b[1;35mSummary:\u001b[0m + + DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. + + \u001b[1;31mGoodbye! We hope to see you again soon.\u001b[0m + `); + }); }; export default initializeApp; diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index 7e77f2c9..995f295d 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -1,106 +1,125 @@ flowchart LR 0["server.ts"] -subgraph 1["controllers"] -2["highAvailability.ts"] -3["scheduler.ts"] -6["fetchData.ts"] -K["frontendConfiguration.ts"] +subgraph 1["config"] +2["hostsystem.ts"] +B["db.ts"] +1G["swaggerConfig.ts"] end -subgraph 4["config"] -5["db.ts"] -19["swaggerConfig.ts"] +3["os"] +subgraph 4["controllers"] +5["highAvailability.ts"] +9["proxy.ts"] +A["scheduler.ts"] +C["fetchData.ts"] +R["frontendConfiguration.ts"] end -subgraph 7["utils"] -8["containerService.ts"] -9["dockerClient.ts"] -N["connectionChecker.ts"] -P["extractHostData.ts"] -Q["writeOfflineLog.ts"] -subgraph V["notifications"] -W["_notify.ts"] -X["discord.ts"] -Y["_template.ts"] -Z["email.ts"] -10["pushbullet.ts"] -11["pushover.ts"] -12["slack.ts"] -13["telegram.ts"] -14["whatsapp.ts"] +6["util"] +7["init.ts"] +8["process"] +subgraph D["utils"] +E["containerService.ts"] +F["dockerClient.ts"] +U["connectionChecker.ts"] +W["extractHostData.ts"] +X["writeOfflineLog.ts"] +subgraph 12["notifications"] +13["_notify.ts"] +14["discord.ts"] +16["_template.ts"] +17["email.ts"] +18["pushbullet.ts"] +19["pushover.ts"] +1A["slack.ts"] +1B["telegram.ts"] +1C["whatsapp.ts"] end +1F["swaggerDocs.ts"] end -subgraph A["middleware"] -B["authMiddleware.ts"] -C["rateLimiter.ts"] +subgraph G["middleware"] +H["authMiddleware.ts"] +I["checkLock.ts"] +J["rateLimiter.ts"] end -subgraph D["routes"] -subgraph E["auth"] -F["routes.ts"] -end -subgraph G["data"] -H["routes.ts"] +subgraph K["routes"] +subgraph L["auth"] +M["routes.ts"] end -subgraph I["frontendController"] -J["routes.ts"] +subgraph N["data"] +O["routes.ts"] end -subgraph L["getter"] -M["routes.ts"] +subgraph P["frontendController"] +Q["routes.ts"] end -subgraph R["highavailability"] -S["routes.ts"] +subgraph S["getter"] +T["routes.ts"] end -subgraph T["notifications"] -U["routes.ts"] +subgraph Y["highavailability"] +Z["routes.ts"] end -subgraph 15["setter"] -16["routes.ts"] +subgraph 10["notifications"] +11["routes.ts"] end +subgraph 1D["setter"] +1E["routes.ts"] end -O["net"] -subgraph 17["swagger"] -18["swaggerDocs.ts"] end +V["net"] +15["https"] 0-->2 -0-->3 -0-->B -0-->C -0-->F -0-->H -0-->J -0-->M -0-->S -0-->U -0-->16 -0-->18 -3-->5 -3-->6 -6-->5 -6-->8 -8-->9 -H-->5 -J-->K -M-->3 -M-->N -M-->8 -M-->9 -M-->P -M-->Q -N-->O -S-->2 -U-->W -W-->X -W-->Z -W-->10 -W-->11 -W-->12 -W-->13 -W-->14 -X-->Y -Z-->Y -10-->Y -11-->Y -12-->Y -13-->Y -14-->Y -16-->3 -18-->19 +0-->5 +0-->7 +2-->3 +5-->6 +7-->9 +7-->A +7-->H +7-->I +7-->J +7-->M +7-->O +7-->Q +7-->T +7-->Z +7-->11 +7-->1E +7-->1F +7-->8 +A-->B +A-->C +C-->B +C-->E +E-->F +O-->B +Q-->R +T-->A +T-->U +T-->E +T-->F +T-->W +T-->X +U-->V +Z-->5 +11-->13 +13-->14 +13-->17 +13-->18 +13-->19 +13-->1A +13-->1B +13-->1C +14-->16 +14-->15 +17-->16 +18-->16 +18-->15 +19-->16 +19-->15 +1A-->16 +1A-->15 +1B-->16 +1B-->15 +1C-->16 +1C-->15 +1E-->A +1F-->1G diff --git a/src/utils/removeUnusedDeps.sh b/src/utils/removeUnusedDeps.sh index b5b68ebf..df72f4b4 100755 --- a/src/utils/removeUnusedDeps.sh +++ b/src/utils/removeUnusedDeps.sh @@ -2,7 +2,7 @@ echo "Creating unused dependency list" -TMP="$(npx depcheck --ignores @types/supports-color,ipaddr.js,dependency-cruiser,tsx,@types/bcrypt,@types/express,@types/express-handlebars,@types/node,ts-node --quiet --oneline | tail -n 1 | tr -d '\n')" +TMP="$(npx depcheck --ignores @types/node-fetch,uglify-js,@types/supports-color,ipaddr.js,dependency-cruiser,tsx,@types/bcrypt,@types/express,@types/express-handlebars,@types/node,ts-node --quiet --oneline | tail -n 1 | tr -d '\n')" lines=$(echo "$TMP" | tr -s ' ' '\n' | wc -l) diff --git a/yarn.lock b/yarn.lock index 1418f00e..9c800499 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,39 +34,6 @@ call-me-maybe "^1.0.1" z-schema "^5.0.1" -"@babel/generator@7.18.2": - version "7.18.2" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz" - integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== - dependencies: - "@babel/types" "^7.18.2" - "@jridgewell/gen-mapping" "^0.3.0" - jsesc "^2.5.1" - -"@babel/helper-string-parser@^7.18.10": - 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== - -"@babel/helper-validator-identifier@^7.18.6": - version "7.25.9" - resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - -"@babel/parser@7.18.4": - version "7.18.4" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz" - integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== - -"@babel/types@^7.18.2", "@babel/types@7.19.0": - version "7.19.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== - dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" - to-fast-properties "^2.0.0" - "@balena/dockerignore@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz" @@ -103,38 +70,16 @@ resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.5" - resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz" - integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== - dependencies: - "@jridgewell/set-array" "^1.2.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.24" - -"@jridgewell/resolve-uri@^3.0.3", "@jridgewell/resolve-uri@^3.1.0": +"@jridgewell/resolve-uri@^3.0.3": version "3.1.2" resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== -"@jridgewell/set-array@^1.2.1": - version "1.2.1" - resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz" - integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== - -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10": version "1.5.0" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@^0.3.24": - version "0.3.25" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" - integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== - dependencies: - "@jridgewell/resolve-uri" "^3.1.0" - "@jridgewell/sourcemap-codec" "^1.4.14" - "@jridgewell/trace-mapping@0.3.9": version "0.3.9" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" @@ -163,27 +108,6 @@ semver "^7.3.5" tar "^6.1.11" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@npmcli/fs@^1.0.0": version "1.1.1" resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" @@ -493,7 +417,7 @@ ansi-regex@^6.0.1: resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz" integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -544,11 +468,6 @@ array-flatten@1.1.1: resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - asn1@^0.2.6: version "0.2.6" resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" @@ -566,11 +485,6 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -643,7 +557,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.3, braces@~3.0.2: +braces@~3.0.2: version "3.0.3" resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -723,14 +637,6 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" @@ -785,15 +691,6 @@ cli-spinners@^2.9.2: resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - color-convert@^1.9.3: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -901,11 +798,6 @@ cookie@0.7.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz" integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cors@^2.8.5: version "2.8.5" resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" @@ -1025,13 +917,6 @@ diff@^4.0.1: resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - docker-modem@^5.0.3: version "5.0.3" resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz" @@ -1169,11 +1054,6 @@ esbuild@~0.23.0: "@esbuild/win32-ia32" "0.23.1" "@esbuild/win32-x64" "0.23.1" -escalade@^3.1.1: - version "3.2.0" - resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== - escape-html@~1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" @@ -1241,29 +1121,11 @@ fast-deep-equal@^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-glob@^3.2.9: - version "3.3.2" - resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== - dependencies: - "@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" - fast-uri@^3.0.1: version "3.0.3" resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz" integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== -fastq@^1.6.0: - version "1.17.1" - resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz" - integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== - dependencies: - reusify "^1.0.4" - fecha@^4.2.0: version "4.2.3" resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz" @@ -1333,29 +1195,11 @@ fresh@0.5.2: resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== -from2@^2.3.0: - version "2.3.0" - resolved "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz" - integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.0" - fs-constants@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" @@ -1402,11 +1246,6 @@ gauge@^4.0.3: strip-ansi "^6.0.1" wide-align "^1.1.5" -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-east-asian-width@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz" @@ -1438,7 +1277,7 @@ github-from-package@0.0.0: resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== -glob-parent@^5.1.2, glob-parent@~5.1.2: +glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1476,24 +1315,12 @@ global-directory@^4.0.1: dependencies: ini "4.1.1" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - 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" - gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: +graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.11" resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -1525,11 +1352,6 @@ has-unicode@^2.0.1: resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -has@^1.0.3: - version "1.0.4" - resolved "https://registry.npmjs.org/has/-/has-1.0.4.tgz" - integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== - hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" @@ -1606,11 +1428,6 @@ ignore-by-default@^1.0.1: resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== -ignore@^5.2.0: - version "5.3.2" - resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" - integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== - ignore@^6.0.2: version "6.0.2" resolved "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz" @@ -1639,7 +1456,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3, inherits@2, inherits@2.0.4: +inherits@^2.0.3, inherits@^2.0.4, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1659,14 +1476,6 @@ interpret@^3.1.1: resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== -into-stream@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz" - integrity sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== - dependencies: - from2 "^2.3.0" - p-is-promise "^3.0.0" - ip-address@^9.0.5: version "9.0.5" resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz" @@ -1704,13 +1513,6 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.2" -is-core-module@2.9.0: - version "2.9.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" @@ -1771,11 +1573,6 @@ is-unicode-supported@^2.0.0: resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz" integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" @@ -1793,11 +1590,6 @@ jsbn@1.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - json-schema-traverse@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" @@ -1808,15 +1600,6 @@ json5@^2.2.2, json5@^2.2.3: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - kleur@^3.0.3: version "3.0.3" resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" @@ -1920,24 +1703,11 @@ merge-descriptors@1.0.3: resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz" integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - methods@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.4: - version "4.0.8" - resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== - dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" @@ -2056,14 +1826,6 @@ ms@2.0.0: resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -multistream@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz" - integrity sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw== - dependencies: - once "^1.4.0" - readable-stream "^3.6.0" - nan@^2.19.0, nan@^2.20.0: version "2.22.0" resolved "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz" @@ -2101,13 +1863,6 @@ node-domexception@^1.0.0: resolved "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@^2.6.6: - version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" @@ -2251,11 +2006,6 @@ ora@^8.1.1: string-width "^7.2.0" strip-ansi "^7.1.0" -p-is-promise@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz" - integrity sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== - p-map@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" @@ -2283,11 +2033,6 @@ path-to-regexp@0.1.12: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" @@ -2303,50 +2048,11 @@ picomatch@^2.2.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - picomatch@^4.0.2: version "4.0.2" resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== -pkg-fetch@3.4.2: - version "3.4.2" - resolved "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz" - integrity sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA== - dependencies: - chalk "^4.1.2" - fs-extra "^9.1.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.6" - progress "^2.0.3" - semver "^7.3.5" - tar-fs "^2.1.1" - yargs "^16.2.0" - -pkg@^5.8.1: - version "5.8.1" - resolved "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz" - integrity sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA== - dependencies: - "@babel/generator" "7.18.2" - "@babel/parser" "7.18.4" - "@babel/types" "7.19.0" - chalk "^4.1.2" - fs-extra "^9.1.0" - globby "^11.1.0" - into-stream "^6.0.0" - is-core-module "2.9.0" - minimist "^1.2.6" - multistream "^4.1.0" - pkg-fetch "3.4.2" - prebuild-install "7.1.1" - resolve "^1.22.0" - stream-meter "^1.0.4" - playwright-core@1.49.0: version "1.49.0" resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz" @@ -2379,34 +2085,6 @@ prebuild-install@^7.1.1: tar-fs "^2.0.0" tunnel-agent "^0.6.0" -prebuild-install@7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz" - integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - -process-nextick-args@~2.0.0: - 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== - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" @@ -2456,11 +2134,6 @@ qs@6.13.0: dependencies: side-channel "^1.0.6" -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - range-parser@~1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz" @@ -2486,32 +2159,6 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" -readable-stream@^2.0.0: - version "2.3.8" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - 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" - -readable-stream@^2.1.4: - version "2.3.8" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - 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" - readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" @@ -2545,11 +2192,6 @@ regexp-tree@~0.1.1: resolved "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz" integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - require-from-string@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz" @@ -2560,7 +2202,7 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== -resolve@^1.20.0, resolve@^1.22.0: +resolve@^1.20.0: version "1.22.8" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -2582,11 +2224,6 @@ retry@^0.12.0: resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" @@ -2594,23 +2231,11 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - safe-buffer@^5.0.1, safe-buffer@~5.2.0, safe-buffer@5.2.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - safe-regex@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz" @@ -2742,11 +2367,6 @@ sisteransi@^1.0.5: resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" @@ -2824,13 +2444,6 @@ stdin-discarder@^0.2.2: resolved "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz" integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== -stream-meter@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz" - integrity sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ== - dependencies: - readable-stream "^2.1.4" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" @@ -2838,14 +2451,7 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2863,7 +2469,7 @@ string-width@^7.2.0: get-east-asian-width "^1.0.0" strip-ansi "^7.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -2954,17 +2560,7 @@ tar-fs@^2.0.0, tar-fs@~2.0.1: pump "^3.0.0" tar-stream "^2.0.0" -tar-fs@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.1.4" - -tar-stream@^2.0.0, tar-stream@^2.1.4: +tar-stream@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -2997,11 +2593,6 @@ text-hex@1.0.x: resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -3136,17 +2727,12 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - unpipe@~1.0.0, unpipe@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -util-deprecate@^1.0.1, util-deprecate@~1.0.1: +util-deprecate@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== @@ -3234,25 +2820,11 @@ winston@^3.15.0: triple-beam "^1.3.0" winston-transport "^4.9.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - yallist@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" @@ -3263,24 +2835,6 @@ yaml@2.0.0-1: resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz" integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - 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" - yn@3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" From aeaef0a4c1c351a1bac751087f272e78e4870958 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:44:56 +0100 Subject: [PATCH 021/135] Fix: remove verbose flag in workflow Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 87792b05..df4f276c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN apk update && \ COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ -RUN npm install --verbose +RUN npm install COPY ./src ./src RUN npm run build:mini From 0a9f32600694e7843ea7733db0e98a215b6b9dda Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:46:57 +0100 Subject: [PATCH 022/135] Fix: Fixing typo in ReadMe Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae34767f..e6a17bb1 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ With this new release a couple of extra features (compared to v1) are going to b - Multi-arch docker builds (using buildx github action) - Advanced security through middlewares: rate-limiting and authentication - Multi Arch Docker builds through docker buildx -- High Availability using single master and ulimited worker nodes! +- High Availability using single master and unlimited worker nodes! # 🔗 DockStatAPI v2 Documentation From 4af32e873635f9a9d0922544cb678653fd61fde3 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:48:32 +0100 Subject: [PATCH 023/135] Fix: CodeQL Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- src/controllers/frontendConfiguration.ts | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/controllers/frontendConfiguration.ts b/src/controllers/frontendConfiguration.ts index 6a5a6911..e83eaaee 100644 --- a/src/controllers/frontendConfiguration.ts +++ b/src/controllers/frontendConfiguration.ts @@ -185,22 +185,22 @@ async function setIcon(containerName: string, icon: string, custom: boolean) { ); if (custom === true) { - if (containerIndex !== -1) { - data[containerIndex].icon = `custom/${icon}`; - await saveData(data); - } else { - data.push({ name: containerName, icon: `custom/${icon}` }); - await saveData(data); - } - } else { - if (containerIndex !== -1) { - data[containerIndex].icon = `${icon}`; - await saveData(data); - } else { - data.push({ name: containerName, icon: `${icon}` }); - await saveData(data); - } - } + if (containerIndex !== -1) { + data[containerIndex].icon = `custom/${icon}`; + await saveData(data); + } else { + data.push({ name: containerName, icon: `custom/${icon}` }); + await saveData(data); + } + } + else if (containerIndex !== -1) { + data[containerIndex].icon = `${icon}`; + await saveData(data); + } + else { + data.push({ name: containerName, icon: `${icon}` }); + await saveData(data); + } } catch (error: any) { logger.error(error); throw new Error(error); From 00e9ffe396bc85f3709575982a56ff0635f9d025 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:49:15 +0100 Subject: [PATCH 024/135] Fix: Use object destruction Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- src/routes/getter/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index 0f9883f9..c559e637 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -134,7 +134,7 @@ router.get("/system", (req: Request, res: Response) => { * description: Error message detailing the issue encountered. */ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { - const hostName = req.params.hostName; + const {hostName} = req.params; logger.info(`Fetching stats for host: ${hostName}`); if (process.env.OFFLINE === "true") { logger.info("Fetching offline Host Stats"); From 47ff28c6692eba5e5f62ffa57c2a5e96c454a285 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:49:55 +0100 Subject: [PATCH 025/135] Fix: CodeQL (braces for if clauses) Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- src/utils/notifications/_template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index ecc327e1..2b6e3a42 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -44,7 +44,7 @@ function renderTemplate(containerId: string) { let containerData = null; for (const host in containers) { containerData = containers[host].find((c: any) => c.id === containerId); - if (containerData) break; + if (containerData) { } if (!containerData) { From 5404206ea5e649a6ac4ebeed828a5d7ce2783a94 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:52:12 +0100 Subject: [PATCH 026/135] Chore: Update Dockerfile --- Dockerfile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index df4f276c..7bce2028 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ LABEL description="API for DockStat" LABEL license="BSD-3-Clause license" LABEL repository="https://github.com/its4nik/dockstatapi" LABEL documentation="https://github.com/its4nik/dockstatapi" -LABEL org.opencontainers.image.description "The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" +LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" LABEL org.opencontainers.image.licenses="BSD-3-Clause license" LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" @@ -48,8 +48,6 @@ RUN node src/config/db.js # Stage 3: Production stage FROM alpine AS production -ARG RUNNING_IN_DOCKER=true -RUN apk add --update bash nodejs WORKDIR /api From 7e2ca2049c43bea91a9b3a944d556e11298d156b Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:55:10 +0100 Subject: [PATCH 027/135] Chore: Update _template.ts --- src/utils/notifications/_template.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index 2b6e3a42..88439945 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -45,6 +45,7 @@ function renderTemplate(containerId: string) { for (const host in containers) { containerData = containers[host].find((c: any) => c.id === containerId); if (containerData) { + break; } if (!containerData) { From 71b8e2fa1a66a59e75561ab21dbad8bbd5187c08 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 21 Dec 2024 13:56:35 +0100 Subject: [PATCH 028/135] Update _template.ts --- src/utils/notifications/_template.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index 88439945..bbec96f4 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -45,7 +45,7 @@ function renderTemplate(containerId: string) { for (const host in containers) { containerData = containers[host].find((c: any) => c.id === containerId); if (containerData) { - break; + break(); } if (!containerData) { From b0c266cf6df94413f1f71a6f052c56f55547e19b Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 08:30:49 +0100 Subject: [PATCH 029/135] Chore: Add npm run docker(:build) commands --- .gitignore | 1 + docker-compose.yaml | 23 +++++++++++++++++++++++ package.json | 14 ++++++++------ src/data/usePassword.txt | 2 +- 4 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 docker-compose.yaml diff --git a/.gitignore b/.gitignore index 43ddf882..fbbbb21b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ src/data/user.conf src/data/password.json src/data/ha.lock +docker .test* # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..721b8cc7 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,23 @@ +services: + master: + container_name: master + environment: + - NODE_ENV=development + - HA_MASTER=true + - HA_MASTER_IP=localhost:9876 + - HA_NODE=localhost:6789 + - HA_UNSAFE=true + volumes: + - ./docker/master:/api/src/data + ports: + - 9876:9876 + image: dockstatapi:local + slave: + container_name: slave + environment: + - NODE_ENV=development + volumes: + - ./docker/slave:/api/src/data + ports: + - 6789:6789 + image: dockstatapi:local diff --git a/package.json b/package.json index 5e85eced..9517b02f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dockstatapi", - "version": "2", + "version": "2.0.0", "description": "API for docker hosts using dockerode", "main": "src/server.ts", "scripts": { @@ -12,16 +12,14 @@ "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh", "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", - "mini": "bash ./src/misc/minifyDist.sh" + "mini": "bash ./src/misc/minifyDist.sh", + "docker": "sudo docker compose up", + "docker:build": "sudo docker build . -t \"dockstatapi:local\" && sudo docker compose up" }, "keywords": [], "author": "Its4Nik", "license": "BSD 3-Clause License", "dependencies": { - "@types/dockerode": "^3.3.31", - "@types/supports-color": "^8.1.3", - "@types/swagger-jsdoc": "^6.0.4", - "@types/swagger-ui-express": "^4.1.7", "bcrypt": "^5.1.1", "chokidar": "^4.0.1", "cors": "^2.8.5", @@ -38,6 +36,10 @@ "winston": "^3.15.0" }, "devDependencies": { + "@types/dockerode": "^3.3.31", + "@types/supports-color": "^8.1.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", diff --git a/src/data/usePassword.txt b/src/data/usePassword.txt index 02e4a84d..c508d536 100644 --- a/src/data/usePassword.txt +++ b/src/data/usePassword.txt @@ -1 +1 @@ -false \ No newline at end of file +false From 72d4c12e884e3e1c2fc73cb6308c1be00b729379 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 08:35:06 +0100 Subject: [PATCH 030/135] Fix: Fixing 'break' syntax, in _template.ts --- Dockerfile | 2 +- src/utils/notifications/_template.ts | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 7bce2028..59a59ca7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,7 @@ WORKDIR /build RUN mkdir -p /build/src/data COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ -RUN npm install --omit=dev --verbose +RUN npm install --omit=dev COPY --from=builder /build/dist/* /build/src COPY --from=builder /build/src/misc/entrypoint.sh /build/entrypoint.sh diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index bbec96f4..551da826 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -1,13 +1,14 @@ import fs from "fs"; import logger from "../logger"; + const templatePath: string = "./src/data/template.json"; const containersPath: string = "./src/data/states.json"; interface Template { - "text": string + text: string; } -function getTemplate() { +function getTemplate(): Template | null { try { const data = fs.readFileSync(templatePath, "utf8"); return JSON.parse(data); @@ -17,11 +18,11 @@ function getTemplate() { } } -function setTemplate(newTemplate: string) { +function setTemplate(newTemplate: string): void { try { fs.writeFileSync( templatePath, - JSON.stringify(newTemplate, null, 2), + JSON.stringify({ text: newTemplate }, null, 2), "utf8", ); logger.debug("Template updated successfully"); @@ -30,8 +31,8 @@ function setTemplate(newTemplate: string) { } } -function renderTemplate(containerId: string) { - const template: Template = getTemplate(); +function renderTemplate(containerId: string): string | null { + const template = getTemplate(); if (!template) { logger.error("Template is missing or not a string"); return null; @@ -41,11 +42,12 @@ function renderTemplate(containerId: string) { const data = fs.readFileSync(containersPath, "utf8"); const containers = JSON.parse(data); - let containerData = null; + let containerData: Record | null = null; for (const host in containers) { containerData = containers[host].find((c: any) => c.id === containerId); if (containerData) { - break(); + break; + } } if (!containerData) { @@ -65,5 +67,4 @@ function renderTemplate(containerId: string) { } } - export { getTemplate, setTemplate, renderTemplate }; From 84cfbf39ee4847d5cce931d15930354d63849da2 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 08:45:58 +0100 Subject: [PATCH 031/135] Feat: Add CI/CD Badges to ReadMe --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index e6a17bb1..6fd1f001 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # DockStatAPI v2 ![Dockstat Logo](.github/DockStat.png) +*Pipelines:* +Main: [![GitHubCI pipeline status badge](https://github.com/its4nik/dokstatapi/workflows/build-main/badge.svg?branch=main)](https://github.com/its4nik/dokstatapi/commits/main) +Dev : [![GitHubCI pipeline status badge](https://github.com/its4nik/dokstatapi/workflows/build-dev/badge.svg?branch=dev)](https://github.com/its4nik/dokstatapi/commits/dev) + This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. With this new release a couple of extra features (compared to v1) are going to be available. From 1e16f950e452ca0bd6135b2b19abe75ca03e678c Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 08:47:50 +0100 Subject: [PATCH 032/135] Fix: Change badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6fd1f001..034c0095 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ![Dockstat Logo](.github/DockStat.png) *Pipelines:* -Main: [![GitHubCI pipeline status badge](https://github.com/its4nik/dokstatapi/workflows/build-main/badge.svg?branch=main)](https://github.com/its4nik/dokstatapi/commits/main) -Dev : [![GitHubCI pipeline status badge](https://github.com/its4nik/dokstatapi/workflows/build-dev/badge.svg?branch=dev)](https://github.com/its4nik/dokstatapi/commits/dev) +[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml) +[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml) This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. With this new release a couple of extra features (compared to v1) are going to be available. From f2322bc907a4495209b8ef881f38019261eaed6b Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 08:48:20 +0100 Subject: [PATCH 033/135] Fix: Update README.md with newlines between the badges --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 034c0095..f602f3b2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # DockStatAPI v2 ![Dockstat Logo](.github/DockStat.png) -*Pipelines:* -[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml) -[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml) +*Pipelines:*
+[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml)
+[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml)
This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. With this new release a couple of extra features (compared to v1) are going to be available. From 1f0c5d652199121745ddba92ad825336bef50375 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 13:23:29 +0100 Subject: [PATCH 034/135] Fix: Needs bash and curl for healthcheck / entrypoint --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index 59a59ca7..5e49bbd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,6 +49,10 @@ RUN node src/config/db.js # Stage 3: Production stage FROM alpine AS production +RUN apk add --update bash curl +HEALTHCHECK --interval=5m --timeout=3s \ + curl -f http://localhost:9876/api/status || exit 1 + WORKDIR /api COPY --from=main /build /api From 0de6ba9c6fe53c5669b3553a0e8a539cb75d1040 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 13:24:45 +0100 Subject: [PATCH 035/135] Fix: Add 'CMD' to HEALTHCHECK --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5e49bbd0..f463522d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -51,7 +51,7 @@ FROM alpine AS production RUN apk add --update bash curl HEALTHCHECK --interval=5m --timeout=3s \ - curl -f http://localhost:9876/api/status || exit 1 + CMD curl -f http://localhost:9876/api/status || exit 1 WORKDIR /api From 522b2d093ba86a88d286cbd91376fa7d1d1d8c34 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 23 Dec 2024 23:27:00 +0100 Subject: [PATCH 036/135] Fix: Adding bash/nodejs binaries to last docker stage --- .gitignore | 1 + Dockerfile | 6 ++--- docker-compose.yaml | 42 +++++++++++++++++++++++++++-- package-lock.json | 32 +++++++++++++++++----- package.json | 6 +++-- src/config/loggerConfig.ts | 11 ++++++++ src/data/frontendConfiguration.json | 8 ++++++ src/data/usePassword.txt | 2 +- src/init.ts | 23 ---------------- src/server.ts | 6 +++-- 10 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 src/data/frontendConfiguration.json diff --git a/.gitignore b/.gitignore index fbbbb21b..c7f5c64e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ src/data/states.json src/data/user.conf src/data/password.json src/data/ha.lock +src/data/frontendConfiguration.json docker .test* diff --git a/Dockerfile b/Dockerfile index f463522d..78ee53b2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:alpine AS builder LABEL maintainer="https://github.com/its4nik" -LABEL version="2" +LABEL version="2.0.0" LABEL description="API for DockStat" LABEL license="BSD-3-Clause license" LABEL repository="https://github.com/its4nik/dockstatapi" @@ -49,9 +49,9 @@ RUN node src/config/db.js # Stage 3: Production stage FROM alpine AS production -RUN apk add --update bash curl +RUN apk add --update bash curl nodejs HEALTHCHECK --interval=5m --timeout=3s \ - CMD curl -f http://localhost:9876/api/status || exit 1 + CMD curl -f http://localhost:9876/api/status || exit 1 WORKDIR /api diff --git a/docker-compose.yaml b/docker-compose.yaml index 721b8cc7..31586575 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -4,8 +4,8 @@ services: environment: - NODE_ENV=development - HA_MASTER=true - - HA_MASTER_IP=localhost:9876 - - HA_NODE=localhost:6789 + - HA_MASTER_IP=127.0.0.1:9876 + - HA_NODE=127.0.0.1:6789 - HA_UNSAFE=true volumes: - ./docker/master:/api/src/data @@ -21,3 +21,41 @@ services: ports: - 6789:6789 image: dockstatapi:local + + test-socket-proxy: + image: lscr.io/linuxserver/socket-proxy:latest + container_name: socket-proxy + environment: + - ALLOW_START=1 #optional + - ALLOW_STOP=1 #optional + - ALLOW_RESTARTS=1 #optional + - AUTH=0 #optional + - BUILD=0 #optional + - COMMIT=0 #optional + - CONFIGS=0 #optional + - CONTAINERS=1 #optional + - DISABLE_IPV6=0 #optional + - DISTRIBUTION=0 #optional + - EVENTS=1 #optional + - EXEC=0 #optional + - IMAGES=0 #optional + - INFO=1 #optional + - NETWORKS=1 #optional + - NODES=1 #optional + - PING=1 #optional + - POST=0 #optional + - PLUGINS=0 #optional + - SECRETS=0 #optional + - SERVICES=0 #optional + - SESSION=0 #optional + - SWARM=0 #optional + - SYSTEM=0 #optional + - TASKS=0 #optional + - VERSION=1 #optional + - VOLUMES=0 #optional + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + restart: unless-stopped + read_only: true + tmpfs: + - /run diff --git a/package-lock.json b/package-lock.json index dcd2ac0a..27899c7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,14 @@ { "name": "dockstatapi", - "version": "2", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dockstatapi", - "version": "2", + "version": "2.0.0", "license": "BSD 3-Clause License", "dependencies": { - "@types/dockerode": "^3.3.31", - "@types/supports-color": "^8.1.3", - "@types/swagger-jsdoc": "^6.0.4", - "@types/swagger-ui-express": "^4.1.7", "bcrypt": "^5.1.1", "chokidar": "^4.0.1", "cors": "^2.8.5", @@ -32,11 +28,15 @@ "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", + "@types/dockerode": "^3.3.31", "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", "@types/node": "^22.9.0", "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", + "@types/supports-color": "^8.1.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", "dependency-cruiser": "^16.5.0", "nodemon": "^3.1.7", "ora": "^8.1.1", @@ -721,6 +721,7 @@ "version": "1.19.5", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", @@ -731,6 +732,7 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -750,6 +752,7 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", "integrity": "sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -760,6 +763,7 @@ "version": "3.3.32", "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz", "integrity": "sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==", + "dev": true, "license": "MIT", "dependencies": { "@types/docker-modem": "*", @@ -771,6 +775,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", @@ -790,6 +795,7 @@ "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -802,6 +808,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -814,12 +821,14 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "22.10.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.20.0" @@ -850,18 +859,21 @@ "version": "6.9.17", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -872,6 +884,7 @@ "version": "1.15.7", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -883,6 +896,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "^18.11.18" @@ -892,6 +906,7 @@ "version": "18.19.67", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -901,24 +916,28 @@ "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, "license": "MIT" }, "node_modules/@types/supports-color": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", "integrity": "sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg==", + "dev": true, "license": "MIT" }, "node_modules/@types/swagger-jsdoc": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz", "integrity": "sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/swagger-ui-express": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz", "integrity": "sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g==", + "dev": true, "license": "MIT", "dependencies": { "@types/express": "*", @@ -5088,6 +5107,7 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, "license": "MIT" }, "node_modules/unique-filename": { diff --git a/package.json b/package.json index 9517b02f..65478aa2 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", "mini": "bash ./src/misc/minifyDist.sh", - "docker": "sudo docker compose up", - "docker:build": "sudo docker build . -t \"dockstatapi:local\" && sudo docker compose up" + "docker": "sudo docker compose up -d", + "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", + "docker:build": "docker build . -t \"dockstatapi:local\" && docker compose up -d", + "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down" }, "keywords": [], "author": "Its4Nik", diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts index 7d34f035..5d1a33e4 100644 --- a/src/config/loggerConfig.ts +++ b/src/config/loggerConfig.ts @@ -8,6 +8,16 @@ const green = "\x1b[32m"; const yellow = "\x1b[33m"; const blue = "\x1b[34m"; +const ignoreExitListenerLogs = format((info) => { + if ( + typeof info.message === "string" && + info.message.includes("Exit listeners detected") + ) { + return false; // Silences annoying logs + } + return info; +}); + function colorLog(level: string, levelName: string) { switch (level) { case "info": @@ -26,6 +36,7 @@ function colorLog(level: string, levelName: string) { const logger = createLogger({ level: "debug", format: format.combine( + ignoreExitListenerLogs(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), format.printf((info) => { const level = info.level.toUpperCase().padEnd(5, " "); diff --git a/src/data/frontendConfiguration.json b/src/data/frontendConfiguration.json new file mode 100644 index 00000000..4697f960 --- /dev/null +++ b/src/data/frontendConfiguration.json @@ -0,0 +1,8 @@ +[ + { + "name": "test", + "tags": [ + "123" + ] + } +] \ No newline at end of file diff --git a/src/data/usePassword.txt b/src/data/usePassword.txt index c508d536..02e4a84d 100644 --- a/src/data/usePassword.txt +++ b/src/data/usePassword.txt @@ -1 +1 @@ -false +false \ No newline at end of file diff --git a/src/init.ts b/src/init.ts index 6d3854a0..feaa00d1 100644 --- a/src/init.ts +++ b/src/init.ts @@ -47,29 +47,6 @@ const initializeApp = (app: express.Application): void => { process.on("exit", (code: number) => { logger.warn(`Server exiting (Code: ${code})`); - console.log(` - \u001b[1;31mThank you for using\u001b[0m - - \u001b[1;34m###### ###### #### ### ### #### ######### ###### #########\u001b[0m - \u001b[1;34m### ### ### ### ### ### ### ### ### ### ### ###\u001b[0m - \u001b[1;34m### ### ### ### ### ###### #### ### ### ### ###\u001b[0m - \u001b[1;34m### ### ### ### ### ### ### #### ### ############ ###\u001b[0m - \u001b[1;34m### ### ### ### ### ### ### #### ### ### ### ###\u001b[0m - \u001b[1;34m###### ###### #### ### ### #### ### ### ### ### \u001b[0m(\u001b[1;33mAPI - v2.0.0\u001b[0m) - - \u001b[1;36mUseful links before you go:\u001b[0m - - - Documentation: \u001b[1;32mhttps://outline.itsnik.de/s/dockstat\u001b[0m - - GitHub (Frontend): \u001b[1;32mhttps://github.com/its4nik/dockstat\u001b[0m - - GitHub (Backend): \u001b[1;32mhttps://github.com/its4nik/dockstatapi\u001b[0m - - API Documentation: \u001b[1;32mhttp://localhost:7000/api-docs\u001b[0m - - \u001b[1;35mSummary:\u001b[0m - - DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simple but extensible API which allows queries via a REST endpoint. - - \u001b[1;31mGoodbye! We hope to see you again soon.\u001b[0m - `); }); }; diff --git a/src/server.ts b/src/server.ts index 4853204b..6b680291 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,11 +7,13 @@ import writeUserConf from "./config/hostsystem"; const app = express(); const PORT: number = 9876; +logger.info("Server starting up..."); +logger.info(`Server is running on http://localhost:${PORT}`); +logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs\n`); + writeUserConf(); initializeApp(app); app.listen(PORT, () => { - logger.info(`Server is running on http://localhost:${PORT}`); - logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs`); startMasterNode(); }); From 45e3fc1f2f167a38ec011a198f27d810630e3016 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 25 Dec 2024 20:16:46 +0100 Subject: [PATCH 037/135] Chore: adjust process env and patch to 2.0.1 --- Dockerfile | 2 +- nodemon.json | 11 +++++- package.json | 17 +++----- src/config/hostsystem.ts | 5 ++- src/config/variables.ts | 24 ++++++++++++ src/controllers/fetchData.ts | 26 ++++++------ src/controllers/highAvailability.ts | 26 +++++++----- src/controllers/notificationController.ts | 48 +++++++++++------------ src/controllers/proxy.ts | 3 +- src/data/template.json | 3 ++ src/init.ts | 22 +++++------ src/misc/createEnvDev.sh | 32 +++++++++++++++ src/misc/createEnvFile.sh | 13 +++--- src/misc/entrypoint.sh | 5 +-- src/routes/getter/routes.ts | 35 +++++++---------- src/utils/notifications/_notify.ts | 33 ---------------- src/utils/notifications/discord.ts | 19 ++++----- src/utils/notifications/email.ts | 14 +++++-- src/utils/notifications/pushbullet.ts | 4 +- src/utils/notifications/pushover.ts | 25 ++++++------ src/utils/notifications/slack.ts | 19 ++++----- src/utils/notifications/telegram.ts | 23 +++++------ src/utils/notifications/whatsapp.ts | 21 +++++----- tsconfig.json | 12 ++---- 24 files changed, 233 insertions(+), 209 deletions(-) create mode 100644 src/config/variables.ts create mode 100644 src/data/template.json create mode 100755 src/misc/createEnvDev.sh diff --git a/Dockerfile b/Dockerfile index 78ee53b2..53f3b729 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM node:alpine AS builder LABEL maintainer="https://github.com/its4nik" -LABEL version="2.0.0" +LABEL version="2.0.1" LABEL description="API for DockStat" LABEL license="BSD-3-Clause license" LABEL repository="https://github.com/its4nik/dockstatapi" diff --git a/nodemon.json b/nodemon.json index 30602eb0..9d946e97 100644 --- a/nodemon.json +++ b/nodemon.json @@ -1,6 +1,13 @@ { - "ignore": ["src/logs", "**/fixtures/**", ".gitignore", "**/*.json"], + "ignore": [ + "**/data/**", + "src/logs", + "**/fixtures/**", + ".gitignore", + "**/*.json" + ], "execMap": { "ts": "tsx" - } + }, + "delay": 2500 } diff --git a/package.json b/package.json index 65478aa2..d600a792 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,14 @@ { "name": "dockstatapi", - "version": "2.0.0", + "version": "2.0.1", "description": "API for docker hosts using dockerode", "main": "src/server.ts", "scripts": { - "start": "tsx src/server.ts", + "local-env-file": "bash ./src/misc/createEnvDev.sh", + "start": "npm run local-env-file && tsx src/server.ts", "start:build": "npx tsc && node dist/server.js", - "dev": "nodemon", - "dev:trace": "nodemon --trace-uncaught --trace-warnings", + "dev": "npm run local-env-file && nodemon", + "dev:trace": "npm run local-env-file && nodemon --trace-uncaught --trace-warnings", "dep": "bash ./src/utils/createDependencyGraph.sh", "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh", "build": "npx tsc", @@ -57,14 +58,6 @@ "tsx": "^4.19.2", "uglify-js": "^3.19.3" }, - "nodemonConfig": { - "ignore": [ - "**/data/**", - "**/*.json", - ".gitignore" - ], - "delay": 2500 - }, "engines": { "npm": ">=10.8.2" }, diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts index 520d3efd..8b4227f4 100644 --- a/src/config/hostsystem.ts +++ b/src/config/hostsystem.ts @@ -1,10 +1,11 @@ +import { RUNNING_IN_DOCKER, VERSION } from "./variables"; import fs from "fs"; import logger from "../utils/logger"; import os from "os"; const userConf = "./src/data/user.conf"; -const inDocker: boolean = !!process.env.RUNNING_IN_DOCKER; -const version: string = process.env.VERSION || "unknown"; +const inDocker: boolean = RUNNING_IN_DOCKER == "true"; +const version: string = VERSION || "unknown"; function writeUserConf() { let previousConfig = null; diff --git a/src/config/variables.ts b/src/config/variables.ts new file mode 100644 index 00000000..26a522be --- /dev/null +++ b/src/config/variables.ts @@ -0,0 +1,24 @@ +import vars from "../data/variables.json"; + +export const { + VERSION, + RUNNING_IN_DOCKER, + TRUSTED_PROXYS, + HA_MASTER, + HA_MASTER_IP, + HA_NODE, + HA_UNSAFE, + DISCORD_WEBHOOK_URL, + EMAIL_SENDER, + EMAIL_RECIPIENT, + EMAIL_PASSWORD, + EMAIL_SERVICE, + PUSHBULLET_ACCESS_TOKEN, + PUSHOVER_USER_KEY, + PUSHOVER_API_TOKEN, + SLACK_WEBHOOK_URL, + TELEGRAM_BOT_TOKEN, + TELEGRAM_CHAT_ID, + WHATSAPP_API_URL, + WHATSAPP_RECIPIENT, +} = vars; diff --git a/src/controllers/fetchData.ts b/src/controllers/fetchData.ts index be9fdc7e..238e8262 100644 --- a/src/controllers/fetchData.ts +++ b/src/controllers/fetchData.ts @@ -22,21 +22,17 @@ const fetchData = async (): Promise => { const allContainerData: AllContainerData = (await fetchAllContainers()) || {}; - if (process.env.OFFLINE === "true") { - logger.info("No new data inserted --- OFFLINE MODE"); - } else { - db.run( - `INSERT INTO data (info) VALUES (?)`, - [JSON.stringify(allContainerData)], - function (error) { - if (error) { - logger.error("Error inserting data:", error); - return; - } - logger.info(`Data inserted with ID: ${this.lastID}`); - }, - ); - } + db.run( + `INSERT INTO data (info) VALUES (?)`, + [JSON.stringify(allContainerData)], + function (error) { + if (error) { + logger.error("Error inserting data:", error); + return; + } + logger.info(`Data inserted with ID: ${this.lastID}`); + }, + ); const containerStatus: AllContainerData = {}; diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index e8557574..f02bde9d 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -3,6 +3,12 @@ import fs from "fs"; import chokidar from "chokidar"; import path from "path"; import { promisify } from "util"; +import { + HA_UNSAFE, + HA_MASTER, + HA_MASTER_IP, + HA_NODE, +} from "../config/variables"; const sleep = promisify(setTimeout); @@ -28,7 +34,7 @@ interface NodeCache { const haMasterPath: string = "./src/data/highAvailability.json"; const haNodePath: string = "./src/data/haNode.json"; const nodeCachePath: string = "./src/data/nodeCache.json"; -const useUnsafeConnection = process.env.HA_UNSAFE || "false"; +const useUnsafeConnection: boolean = HA_UNSAFE == "false"; const lockFilePath: string = "./src/data/ha.lock"; const configFiles: string[] = [ @@ -39,6 +45,7 @@ const configFiles: string[] = [ "./src/data/nodeCache.json", "./src/data/usePassword.txt", "./src/data/password.json", + "./src/data/variables.json", ]; async function acquireLock(): Promise { @@ -119,7 +126,7 @@ async function prepareFilesForSync(): Promise> { async function checkApiReachable(node: string): Promise { let nodeUrl = - useUnsafeConnection === "true" + useUnsafeConnection === true ? `http://${node}/api/status` : `https://${node}/api/status`; @@ -163,7 +170,7 @@ async function synchronizeFilesWithNodes(): Promise { } let nodeUrl = - useUnsafeConnection == "true" + useUnsafeConnection == true ? `http://${node}/ha/sync` : `https://${node}/ha/sync`; @@ -201,8 +208,9 @@ function monitorConfigFiles(): void { } async function startMasterNode() { - if (process.env.HA_MASTER == "true") { - if (!process.env.HA_MASTER_IP) { + let isMaster: boolean = HA_MASTER == "false"; + if (isMaster) { + if (!HA_MASTER_IP) { logger.error( "Master's IP is not set, please set the HA_MASTER_IP variable (example: 10.0.0.4:9876)", ); @@ -213,13 +221,11 @@ async function startMasterNode() { const haConfig: HighAvailabilityConfig = { active: true, master: true, - nodes: process.env.HA_NODE - ? process.env.HA_NODE.split(",").map((node) => node.trim()) - : [], + nodes: HA_NODE ? HA_NODE.split(",").map((node) => node.trim()) : [], }; - const nodeCache: NodeCache = process.env.HA_NODE - ? process.env.HA_NODE.split(",").reduce((cache, node, index) => { + const nodeCache: NodeCache = HA_NODE + ? HA_NODE.split(",").reduce((cache, node, index) => { const [ip, id] = node.trim().split(":"); if (ip && id) { cache[`node${index + 1}`] = { ip, id: parseInt(id, 10) }; diff --git a/src/controllers/notificationController.ts b/src/controllers/notificationController.ts index e34eecda..ad0b1bcc 100644 --- a/src/controllers/notificationController.ts +++ b/src/controllers/notificationController.ts @@ -1,20 +1,29 @@ import notify from "../utils/notifications/_notify"; import logger from "../utils/logger"; +import { + DISCORD_WEBHOOK_URL, + EMAIL_SENDER, + EMAIL_RECIPIENT, + EMAIL_PASSWORD, + EMAIL_SERVICE, + PUSHBULLET_ACCESS_TOKEN, + PUSHOVER_USER_KEY, + PUSHOVER_API_TOKEN, + SLACK_WEBHOOK_URL, + TELEGRAM_BOT_TOKEN, + TELEGRAM_CHAT_ID, + WHATSAPP_API_URL, + WHATSAPP_RECIPIENT, +} from "../config/variables"; const notificationTypes = { - discord: !!process.env.DISCORD_WEBHOOK_URL, - email: !!( - process.env.EMAIL_SENDER && - process.env.EMAIL_RECIPIENT && - process.env.EMAIL_PASSWORD - ), - pushbullet: !!process.env.PUSHBULLET_ACCESS_TOKEN, - pushover: !!(process.env.PUSHOVER_API_TOKEN && process.env.PUSHOVER_USER_KEY), - slack: !!process.env.SLACK_WEBHOOK_UR, - telegram: !!(process.env.TELEGRAM_BOT_TOKEN && process.env.TELEGRAM_CHAT_ID), - whatsapp: !!(process.env.WHATSAPP_API_URL && process.env.WHATSAPP_RECIPIENT), - custom: !!process.env.CUSTOM_NOTIFICATION, - customList: process.env.CUSTOM_NOTIFICATION, + discord: !!DISCORD_WEBHOOK_URL, + email: !!(EMAIL_SENDER && EMAIL_RECIPIENT && EMAIL_PASSWORD && EMAIL_SERVICE), + pushbullet: !!PUSHBULLET_ACCESS_TOKEN, + pushover: !!(PUSHOVER_API_TOKEN && PUSHOVER_USER_KEY), + slack: !!SLACK_WEBHOOK_URL, + telegram: !!(TELEGRAM_BOT_TOKEN && TELEGRAM_CHAT_ID), + whatsapp: !!(WHATSAPP_API_URL && WHATSAPP_RECIPIENT), }; async function sendNotification(containerId: string) { @@ -46,17 +55,4 @@ async function sendNotification(containerId: string) { logger.debug(`Sending notification via Pushbullet (${containerId})`); notify("whatsapp", containerId); } - if (notificationTypes.custom) { - const elements: undefined | string[] = notificationTypes.customList - ? notificationTypes.customList.split(",") - : undefined; - if (elements) { - elements.forEach((element) => { - logger.debug(`Sending custom notification ${element} (${containerId})`); - notify(`custom/${element}`, containerId); - }); - } else { - logger.error("Error getting custom notifications"); - } - } } diff --git a/src/controllers/proxy.ts b/src/controllers/proxy.ts index 681adef7..601f1556 100644 --- a/src/controllers/proxy.ts +++ b/src/controllers/proxy.ts @@ -1,8 +1,9 @@ import { Application } from "express"; import logger from "../utils/logger"; +import { TRUSTED_PROXYS } from "../config/variables"; export default function trustedProxies(app: Application) { - const trusted: string = process.env.TRUSTED_PROXYS || ""; + const trusted: string = TRUSTED_PROXYS; if (!trusted) { logger.warn( diff --git a/src/data/template.json b/src/data/template.json new file mode 100644 index 00000000..75e12f22 --- /dev/null +++ b/src/data/template.json @@ -0,0 +1,3 @@ +{ + "text": "{{name}} is {{state}} on {{hostName}}" +} diff --git a/src/init.ts b/src/init.ts index feaa00d1..eb3612bf 100644 --- a/src/init.ts +++ b/src/init.ts @@ -16,6 +16,8 @@ import cors from "cors"; import { blockWhileLocked } from "./middleware/checkLock"; import logger from "./utils/logger"; +const LAB = [limiter, authMiddleware, blockWhileLocked]; + const initializeApp = (app: express.Application): void => { app.use(cors()); app.use(express.json()); @@ -24,21 +26,15 @@ const initializeApp = (app: express.Application): void => { ); swaggerDocs(app as any); - trustedProxies(app); // Configures proxies using CSV string + trustedProxies(app); scheduleFetch(); - app.use("/api", limiter, authMiddleware, blockWhileLocked, api); - app.use("/conf", limiter, authMiddleware, blockWhileLocked, conf); - app.use("/auth", limiter, authMiddleware, blockWhileLocked, auth); - app.use("/data", limiter, authMiddleware, blockWhileLocked, data); - app.use("/frontend", limiter, authMiddleware, blockWhileLocked, frontend); - app.use( - "/notification-service", - limiter, - authMiddleware, - blockWhileLocked, - notificationService, - ); + app.use("/api", LAB, api); + app.use("/conf", LAB, conf); + app.use("/auth", LAB, auth); + app.use("/data", LAB, data); + app.use("/frontend", LAB, frontend); + app.use("/notification-service", LAB, notificationService); app.use("/ha", limiter, authMiddleware, ha); app.get("/", (req: Request, res: Response) => { diff --git a/src/misc/createEnvDev.sh b/src/misc/createEnvDev.sh new file mode 100755 index 00000000..dde36f63 --- /dev/null +++ b/src/misc/createEnvDev.sh @@ -0,0 +1,32 @@ +VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" + +if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then + RUNNING_IN_DOCKER="true" +else + RUNNING_IN_DOCKER="false" +fi + +echo -n "\ +{ + \"VERSION\": \"${VERSION}\", + \"RUNNING_IN_DOCKER\": \"${RUNNING_IN_DOCKER}\", + \"TRUSTED_PROXYS\": \"${TRUSTED_PROXYS}\", + \"HA_MASTER\": \"${HA_MASTER}\", + \"HA_MASTER_IP\": \"${HA_MASTER_IP}\", + \"HA_NODE\": \"${HA_NODE}\", + \"HA_UNSAFE\": \"${HA_UNSAFE}\", + \"DISCORD_WEBHOOK_URL\": \"${DISCORD_WEBHOOK_URL}\", + \"EMAIL_SENDER\": \"${EMAIL_SENDER}\", + \"EMAIL_RECIPIENT\": \"${EMAIL_RECIPIENT}\", + \"EMAIL_PASSWORD\": \"${EMAIL_PASSWORD}\", + \"EMAIL_SERVICE\": \"${EMAIL_SERVICE}\", + \"PUSHBULLET_ACCESS_TOKEN\": \"${PUSHBULLET_ACCESS_TOKEN}\", + \"PUSHOVER_USER_KEY\": \"${PUSHOVER_USER_KEY}\", + \"PUSHOVER_API_TOKEN\": \"${PUSHOVER_API_TOKEN}\", + \"SLACK_WEBHOOK_URL\": \"${SLACK_WEBHOOK_URL}\", + \"TELEGRAM_BOT_TOKEN\": \"${TELEGRAM_BOT_TOKEN}\", + \"TELEGRAM_CHAT_ID\": \"${TELEGRAM_CHAT_ID}\", + \"WHATSAPP_API_URL\": \"${WHATSAPP_API_URL}\", + \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\" +} \ +" > ./src/data/variables.json diff --git a/src/misc/createEnvFile.sh b/src/misc/createEnvFile.sh index cbd8244d..d47eaa9c 100644 --- a/src/misc/createEnvFile.sh +++ b/src/misc/createEnvFile.sh @@ -1,7 +1,7 @@ #!/bin/bash # Version -VERSION="$1" +VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" # Docker if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then @@ -9,9 +9,11 @@ if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then else RUNNING_IN_DOCKER="false" fi -echo " +echo -n "\ { + \"VERSION\": \"${VERSION}\", \"RUNNING_IN_DOCKER\": \"${RUNNING_IN_DOCKER}\", + \"TRUSTED_PROXYS\": \"${TRUSTED_PROXYS}\", \"HA_MASTER\": \"${HA_MASTER}\", \"HA_MASTER_IP\": \"${HA_MASTER_IP}\", \"HA_NODE\": \"${HA_NODE}\", @@ -28,7 +30,6 @@ echo " \"TELEGRAM_BOT_TOKEN\": \"${TELEGRAM_BOT_TOKEN}\", \"TELEGRAM_CHAT_ID\": \"${TELEGRAM_CHAT_ID}\", \"WHATSAPP_API_URL\": \"${WHATSAPP_API_URL}\", - \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\", - \"CUSTOM_NOTIFICATION\": \"${CUSTOM_NOTIFICATION}\" -} -" > /api/src/data/variables.conf + \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\" +} \ +" > /api/src/data/variables.json diff --git a/src/misc/entrypoint.sh b/src/misc/entrypoint.sh index ff5cc617..83eaf46d 100755 --- a/src/misc/entrypoint.sh +++ b/src/misc/entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash -VERSION="2.0.0" +VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" echo -e " \033[1;32mWelcome to\033[0m @@ -17,7 +17,6 @@ echo -e " - Documentation: \033[1;32mhttps://outline.itsnik.de/s/dockstat\033[0m - GitHub (Frontend): \033[1;32mhttps://github.com/its4nik/dockstat\033[0m - GitHub (Backend): \033[1;32mhttps://github.com/its4nik/dockstatapi\033[0m -- API Documentation: \033[1;32mhttp://localhost:7000/api-docs\033[0m \033[1;35mSummary:\033[0m @@ -25,6 +24,6 @@ DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simpl " -bash "./createEnvFile.sh" "$VERSION" +bash "./createEnvFile.sh" exec node src/server.js diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index c559e637..b6c89c12 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -134,28 +134,23 @@ router.get("/system", (req: Request, res: Response) => { * description: Error message detailing the issue encountered. */ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { - const {hostName} = req.params; + const { hostName } = req.params; logger.info(`Fetching stats for host: ${hostName}`); - if (process.env.OFFLINE === "true") { - logger.info("Fetching offline Host Stats"); - res.status(200).json(readOfflineLog); - } else { - try { - const docker = getDockerClient(hostName); - const info = await docker.info(); - const version = await docker.version(); - const relevantData = extractRelevantData({ hostName, info, version }); + try { + const docker = getDockerClient(hostName); + const info = await docker.info(); + const version = await docker.version(); + const relevantData = extractRelevantData({ hostName, info, version }); - writeOfflineLog(JSON.stringify(relevantData)); - res.status(200).json(relevantData); - } catch (error: any) { - logger.error( - `Error fetching stats for host: ${hostName} - ${error.message || "Unknown error"}`, - ); - res.status(500).json({ - error: `Error fetching host stats: ${error.message || "Unknown error"}`, - }); - } + writeOfflineLog(JSON.stringify(relevantData)); + res.status(200).json(relevantData); + } catch (error: any) { + logger.error( + `Error fetching stats for host: ${hostName} - ${error.message || "Unknown error"}`, + ); + res.status(500).json({ + error: `Error fetching host stats: ${error.message || "Unknown error"}`, + }); } }); diff --git a/src/utils/notifications/_notify.ts b/src/utils/notifications/_notify.ts index 018b3dce..139a0066 100644 --- a/src/utils/notifications/_notify.ts +++ b/src/utils/notifications/_notify.ts @@ -6,28 +6,6 @@ import { emailNotification } from "./email"; import { whatsappNotification } from "./whatsapp"; import { pushbulletNotification } from "./pushbullet"; import { pushoverNotification } from "./pushover"; -import path from "path"; - -async function loadCustomNotification(scriptPath: string, containerId: string) { - try { - const absolutePath = path.resolve(__dirname, "./custom", scriptPath); - const customModule = await import(absolutePath); - - if (typeof customModule.default !== "function") { - const errorMsg = `The custom notification script at ${scriptPath} does not export a default function.`; - logger.error(errorMsg); - throw new Error(errorMsg); - } - - logger.debug(`Executing custom notification script: ${scriptPath}`); - await customModule.default(containerId); - } catch (error: any) { - logger.error( - `Failed to execute custom notification script (${scriptPath}): ${error.message}`, - ); - throw error; - } -} async function notify(type: string, containerId: string) { if (!containerId) { @@ -35,17 +13,6 @@ async function notify(type: string, containerId: string) { throw new Error("Container ID is required."); } - if (type.startsWith("custom/")) { - const scriptName = type.split("/")[1]; - if (!scriptName) { - const errorMsg = "Custom notification script name is invalid."; - logger.error(errorMsg); - throw new Error(errorMsg); - } - await loadCustomNotification(`${scriptName}.js`, containerId); - return; - } - switch (type) { case "telegram": logger.debug("Sending Telegram notification..."); diff --git a/src/utils/notifications/discord.ts b/src/utils/notifications/discord.ts index 24aaf905..d9be3a02 100644 --- a/src/utils/notifications/discord.ts +++ b/src/utils/notifications/discord.ts @@ -1,8 +1,9 @@ -import * as https from 'https'; +import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { DISCORD_WEBHOOK_URL } from "../../config/variables"; -const discord_webhook_url: string | undefined = process.env.DISCORD_WEBHOOK_URL; +const discord_webhook_url: string = DISCORD_WEBHOOK_URL; export async function discordNotification(containerId: string): Promise { const discord_message: string | null = renderTemplate(containerId); @@ -25,28 +26,28 @@ export async function discordNotification(containerId: string): Promise { const options = { hostname: url.hostname, path: url.pathname, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData), + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), }, }; const req = https.request(options, (res) => { - let data = ''; + let data = ""; - res.on('data', (chunk) => { + res.on("data", (chunk) => { data += chunk; }); - res.on('end', () => { + res.on("end", () => { if (res.statusCode !== 200) { logger.error(`Discord API error: ${data}`); } }); }); - req.on('error', (error) => { + req.on("error", (error) => { logger.error("Error sending Discord message:", error); }); diff --git a/src/utils/notifications/email.ts b/src/utils/notifications/email.ts index fbefbab6..57c94ef9 100644 --- a/src/utils/notifications/email.ts +++ b/src/utils/notifications/email.ts @@ -1,11 +1,17 @@ import { SendMailOptions, createTransport } from "nodemailer"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { + EMAIL_SENDER, + EMAIL_SERVICE, + EMAIL_PASSWORD, + EMAIL_RECIPIENT, +} from "../../config/variables"; -const email_sender: string | undefined = process.env.EMAIL_SENDER; -const email_recipient: string | undefined = process.env.EMAIL_RECIPIENT; -const email_password: string | undefined = process.env.EMAIL_PASSWORD; -const email_service: string | undefined = process.env.EMAIL_SERVICE; +const email_sender: string = EMAIL_SENDER; +const email_recipient: string = EMAIL_RECIPIENT; +const email_password: string = EMAIL_PASSWORD; +const email_service: string = EMAIL_SERVICE; export async function emailNotification(containerId: string) { // Validate email configuration parameters diff --git a/src/utils/notifications/pushbullet.ts b/src/utils/notifications/pushbullet.ts index f008e68c..811427a1 100644 --- a/src/utils/notifications/pushbullet.ts +++ b/src/utils/notifications/pushbullet.ts @@ -1,9 +1,9 @@ import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { PUSHBULLET_ACCESS_TOKEN } from "../../config/variables"; -const pushbullet_access_token: string | undefined = - process.env.PUSHBULLET_ACCESS_TOKEN; +const pushbullet_access_token: string = PUSHBULLET_ACCESS_TOKEN; export async function pushbulletNotification( containerId: string, diff --git a/src/utils/notifications/pushover.ts b/src/utils/notifications/pushover.ts index 847c3296..aac71b3b 100644 --- a/src/utils/notifications/pushover.ts +++ b/src/utils/notifications/pushover.ts @@ -1,9 +1,10 @@ -import * as https from 'https'; +import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { PUSHOVER_USER_KEY, PUSHOVER_API_TOKEN } from "../../config/variables"; -const pushover_user_key: string | undefined = process.env.PUSHOVER_USER_KEY; -const pushover_api_token: string | undefined = process.env.PUSHOVER_API_TOKEN; +const pushover_user_key: string = PUSHOVER_USER_KEY; +const pushover_api_token: string = PUSHOVER_API_TOKEN; export async function pushoverNotification(containerId: string): Promise { const pushover_message: string | null = renderTemplate(containerId); @@ -24,30 +25,30 @@ export async function pushoverNotification(containerId: string): Promise { }).toString(); const options = { - hostname: 'api.pushover.net', - path: '/1/messages.json', - method: 'POST', + hostname: "api.pushover.net", + path: "/1/messages.json", + method: "POST", headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Content-Length': Buffer.byteLength(postData), + "Content-Type": "application/x-www-form-urlencoded", + "Content-Length": Buffer.byteLength(postData), }, }; const req = https.request(options, (res) => { - let data = ''; + let data = ""; - res.on('data', (chunk) => { + res.on("data", (chunk) => { data += chunk; }); - res.on('end', () => { + res.on("end", () => { if (res.statusCode !== 200) { logger.error(`Pushover API error: ${data}`); } }); }); - req.on('error', (error) => { + req.on("error", (error) => { logger.error("Error sending Pushover message:", error); }); diff --git a/src/utils/notifications/slack.ts b/src/utils/notifications/slack.ts index b0a8e0b4..e1e7216b 100644 --- a/src/utils/notifications/slack.ts +++ b/src/utils/notifications/slack.ts @@ -1,8 +1,9 @@ -import * as https from 'https'; +import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { SLACK_WEBHOOK_URL } from "../../config/variables"; -const slack_webhook_url: string | undefined = process.env.SLACK_WEBHOOK_URL; +const slack_webhook_url: string = SLACK_WEBHOOK_URL; export async function slackNotification(containerId: string): Promise { const slack_message: string | null = renderTemplate(containerId); @@ -25,28 +26,28 @@ export async function slackNotification(containerId: string): Promise { const options = { hostname: url.hostname, path: url.pathname, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData), + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), }, }; const req = https.request(options, (res) => { - let data = ''; + let data = ""; - res.on('data', (chunk) => { + res.on("data", (chunk) => { data += chunk; }); - res.on('end', () => { + res.on("end", () => { if (res.statusCode !== 200) { logger.error(`Slack API error: ${data}`); } }); }); - req.on('error', (error) => { + req.on("error", (error) => { logger.error("Error sending Slack message:", error); }); diff --git a/src/utils/notifications/telegram.ts b/src/utils/notifications/telegram.ts index 174a12e5..440e0916 100644 --- a/src/utils/notifications/telegram.ts +++ b/src/utils/notifications/telegram.ts @@ -1,9 +1,10 @@ -import * as https from 'https'; +import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID } from "../../config/variables"; -const telegram_bot_token: string | undefined = process.env.TELEGRAM_BOT_TOKEN; -const telegram_chat_id: string | undefined = process.env.TELEGRAM_CHAT_ID; +const telegram_bot_token: string = TELEGRAM_BOT_TOKEN; +const telegram_chat_id: string = TELEGRAM_CHAT_ID; export async function telegramNotification(containerId: string): Promise { const telegram_message: string | null = renderTemplate(containerId); @@ -23,30 +24,30 @@ export async function telegramNotification(containerId: string): Promise { }); const options = { - hostname: 'api.telegram.org', + hostname: "api.telegram.org", path: `/bot${telegram_bot_token}/sendMessage`, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData), + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), }, }; const req = https.request(options, (res) => { - let data = ''; + let data = ""; - res.on('data', (chunk) => { + res.on("data", (chunk) => { data += chunk; }); - res.on('end', () => { + res.on("end", () => { if (res.statusCode !== 200) { logger.error(`Telegram API error: ${data}`); } }); }); - req.on('error', (error) => { + req.on("error", (error) => { logger.error("Error sending message:", error); }); diff --git a/src/utils/notifications/whatsapp.ts b/src/utils/notifications/whatsapp.ts index 178f6d53..1eb7575e 100644 --- a/src/utils/notifications/whatsapp.ts +++ b/src/utils/notifications/whatsapp.ts @@ -1,9 +1,10 @@ -import * as https from 'https'; +import * as https from "https"; import logger from "../logger"; import { renderTemplate } from "./_template"; +import { WHATSAPP_API_URL, WHATSAPP_RECIPIENT } from "../../config/variables"; -const whatsapp_api_url: string | undefined = process.env.WHATSAPP_API_URL; -const whatsapp_recipient: string | undefined = process.env.WHATSAPP_RECIPIENT; +const whatsapp_api_url: string = WHATSAPP_API_URL; +const whatsapp_recipient: string = WHATSAPP_RECIPIENT; export async function whatsappNotification(containerId: string): Promise { const whatsapp_message: string | null = renderTemplate(containerId); @@ -27,28 +28,28 @@ export async function whatsappNotification(containerId: string): Promise { const options = { hostname: url.hostname, path: url.pathname, - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(postData), + "Content-Type": "application/json", + "Content-Length": Buffer.byteLength(postData), }, }; const req = https.request(options, (res) => { - let data = ''; + let data = ""; - res.on('data', (chunk) => { + res.on("data", (chunk) => { data += chunk; }); - res.on('end', () => { + res.on("end", () => { if (res.statusCode !== 200) { logger.error(`WhatsApp API error: ${data}`); } }); }); - req.on('error', (error) => { + req.on("error", (error) => { logger.error("Error sending WhatsApp message:", error); }); diff --git a/tsconfig.json b/tsconfig.json index 8fc3c320..4af6b1d1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "resolveJsonModule": true, "target": "ES2020", "outDir": "dist/src", "module": "CommonJS", @@ -11,11 +12,6 @@ }, "$schema": "https://json.schemastore.org/tsconfig", "display": "Recommended", - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules", - "**/*.spec.ts" - ] -} \ No newline at end of file + "include": ["src/**/*"], + "exclude": ["node_modules", "**/*.spec.ts"] +} From 39445445b7a7c14c59905bb6a3402015ba7fbc20 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 25 Dec 2024 20:17:23 +0100 Subject: [PATCH 038/135] Fix: update .gitignore --- .gitignore | 1 + TODO.md | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c7f5c64e..ee4e7afe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ src/data/user.conf src/data/password.json src/data/ha.lock src/data/frontendConfiguration.json +src/data/variables.json docker .test* diff --git a/TODO.md b/TODO.md index d2659e2d..c4687d72 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,4 @@ -- [ ] Better Offline mode using "faker" library or self written (probably self written) +- [X] ~Better Offline mode using "faker" library or self written (probably self written)~ Not needed since there is a docker-compsoe file for local testing integrated inside the repo - [X] HA compatibility - [X] !!! Needs testing !!! Add automatic notifications when container state changes, according to selected level for notification service - [ ] Image update and update notifications @@ -10,3 +10,4 @@ - [ ] Websockets - [X] Better /api/status endpoint with connection status of each host - [X] Update notification service +- [X] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) From b6338e9270b136eb21f6b652af39e1cc73f6885e Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 25 Dec 2024 23:37:29 +0100 Subject: [PATCH 039/135] Chore: Creating default files on container startup Fix: Fixing env variables Docker: Added dev Dockerfile without Swagger docs --- Dockerfile-dev | 61 +++++++++++++++++++++++++++++ docker-compose.yaml | 21 ++++++++-- package.json | 4 +- src/config/db.ts | 10 ++--- src/config/hostsystem.ts | 3 +- src/config/initFiles.ts | 41 +++++++++++++++++++ src/config/loggerConfig.ts | 1 + src/controllers/highAvailability.ts | 18 ++++----- src/init.ts | 2 + 9 files changed, 138 insertions(+), 23 deletions(-) create mode 100644 Dockerfile-dev create mode 100644 src/config/initFiles.ts diff --git a/Dockerfile-dev b/Dockerfile-dev new file mode 100644 index 00000000..7ad56f09 --- /dev/null +++ b/Dockerfile-dev @@ -0,0 +1,61 @@ +# Stage 1: Build stage +FROM node:alpine AS builder + +LABEL maintainer="https://github.com/its4nik" +LABEL version="2.0.1" +LABEL description="API for DockStat" +LABEL license="BSD-3-Clause license" +LABEL repository="https://github.com/its4nik/dockstatapi" +LABEL documentation="https://github.com/its4nik/dockstatapi" +LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" +LABEL org.opencontainers.image.licenses="BSD-3-Clause license" +LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" + +WORKDIR /build +ENV NODE_NO_WARNINGS=1 + +RUN apk update && \ + apk upgrade && \ + apk add bash + + +COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +RUN npm install + +COPY ./src ./src +RUN npm run build + +# Stage 2: main stage +FROM alpine AS main + +# Needed packages +RUN apk update && \ + apk upgrade && \ + apk add --update npm + +WORKDIR /build + +RUN mkdir -p /build/src/data + +COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +RUN npm install --omit=dev + +COPY --from=builder /build/dist/* /build/src +COPY --from=builder /build/src/misc/entrypoint.sh /build/entrypoint.sh +COPY --from=builder /build/src/misc/createEnvFile.sh /build/createEnvFile.sh + +RUN node src/config/db.js + +# Stage 3: Production stage +FROM alpine AS production + +RUN apk add --update bash curl nodejs +HEALTHCHECK --interval=5m --timeout=3s \ + CMD curl -f http://localhost:9876/api/status || exit 1 + +WORKDIR /api + +COPY --from=main /build /api + +EXPOSE 9876 +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/docker-compose.yaml b/docker-compose.yaml index 31586575..06d1f459 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,17 +1,26 @@ +networks: + shared-network: + driver: bridge + services: master: container_name: master environment: - NODE_ENV=development - HA_MASTER=true - - HA_MASTER_IP=127.0.0.1:9876 - - HA_NODE=127.0.0.1:6789 + - HA_MASTER_IP=master:9876 + - HA_NODE=slave:9876 - HA_UNSAFE=true volumes: - ./docker/master:/api/src/data ports: - 9876:9876 image: dockstatapi:local + networks: + - shared-network + depends_on: + - slave + - test-socket-proxy slave: container_name: slave environment: @@ -19,12 +28,14 @@ services: volumes: - ./docker/slave:/api/src/data ports: - - 6789:6789 + - 6789:9876 image: dockstatapi:local + networks: + - shared-network test-socket-proxy: image: lscr.io/linuxserver/socket-proxy:latest - container_name: socket-proxy + container_name: test-socket-proxy environment: - ALLOW_START=1 #optional - ALLOW_STOP=1 #optional @@ -59,3 +70,5 @@ services: read_only: true tmpfs: - /run + networks: + - shared-network diff --git a/package.json b/package.json index d600a792..eb9f8652 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", "mini": "bash ./src/misc/minifyDist.sh", - "docker": "sudo docker compose up -d", + "docker": "docker compose up -d", "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", - "docker:build": "docker build . -t \"dockstatapi:local\" && docker compose up -d", + "docker:build": "docker build . -t \"dockstatapi:local\" -f ./Dockerfile-dev && docker compose up -d", "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down" }, "keywords": [], diff --git a/src/config/db.ts b/src/config/db.ts index 93972131..80861350 100644 --- a/src/config/db.ts +++ b/src/config/db.ts @@ -15,12 +15,10 @@ const db: sqlite3.Database = new sqlite3.Database( info TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )`, - (tableErr: Error | null) => { - if (tableErr) { - logger.error("Error creating table:", tableErr.message); - } else { - logger.info("Database created / opened successfully"); - } + () => { + logger.info( + "Database created / opened successfully, table is ready.", + ); }, ); } diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts index 8b4227f4..91e44ed5 100644 --- a/src/config/hostsystem.ts +++ b/src/config/hostsystem.ts @@ -53,9 +53,8 @@ function writeUserConf() { backendVersion: version, }; - logger.info("Starting the server..."); logger.info( - `At: ${startDetails.startedAt} - Version: ${startDetails.backendVersion} - Docker: ${installationDetails.inDocker} - Installed as: ${installationDetails.installedBy} - Platform: ${installationDetails.platform} - Arch: ${installationDetails.arch}`, + `Starting at: ${startDetails.startedAt} - Version: ${startDetails.backendVersion} - Docker: ${installationDetails.inDocker} - Installed as: ${installationDetails.installedBy} - Platform: ${installationDetails.platform} - Arch: ${installationDetails.arch}`, ); } diff --git a/src/config/initFiles.ts b/src/config/initFiles.ts new file mode 100644 index 00000000..1f8776a6 --- /dev/null +++ b/src/config/initFiles.ts @@ -0,0 +1,41 @@ +import { writeFileSync, existsSync } from "fs"; +import logger from "../utils/logger"; +import path from "path"; + +const files = [ + { + path: "./src/data/password.json", + content: JSON.stringify( + { + hash: "", + salt: "", + }, + null, + 2, + ), + }, + { path: "./src/data/states.json", content: "{}" }, + { + path: "./src/data/template.json", + content: JSON.stringify( + { text: "{{name}} is {{state}} on {{hostName}}" }, + null, + 2, + ), + }, + { path: "./src/data/frontendConfiguration.json", content: "[]" }, + { path: "./src/data/usePassword.txt", content: "false" }, +]; + +function initFiles(): void { + files.forEach(({ path: filePath, content }) => { + if (!existsSync(filePath)) { + writeFileSync(filePath, content); + logger.info(`Created: ${filePath}`); + } else { + logger.debug(`Skipped (already exists): ${filePath}`); + } + }); +} + +export default initFiles; diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts index 5d1a33e4..45feb5c7 100644 --- a/src/config/loggerConfig.ts +++ b/src/config/loggerConfig.ts @@ -7,6 +7,7 @@ const red = "\x1b[31m"; const green = "\x1b[32m"; const yellow = "\x1b[33m"; const blue = "\x1b[34m"; +const pink = "\x1b[38;5;213m"; // Pink color for sync logs const ignoreExitListenerLogs = format((info) => { if ( diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index f02bde9d..919148e4 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -34,7 +34,7 @@ interface NodeCache { const haMasterPath: string = "./src/data/highAvailability.json"; const haNodePath: string = "./src/data/haNode.json"; const nodeCachePath: string = "./src/data/nodeCache.json"; -const useUnsafeConnection: boolean = HA_UNSAFE == "false"; +const useUnsafeConnection: boolean = JSON.parse(HA_UNSAFE || "false"); const lockFilePath: string = "./src/data/ha.lock"; const configFiles: string[] = [ @@ -45,7 +45,6 @@ const configFiles: string[] = [ "./src/data/nodeCache.json", "./src/data/usePassword.txt", "./src/data/password.json", - "./src/data/variables.json", ]; async function acquireLock(): Promise { @@ -130,6 +129,8 @@ async function checkApiReachable(node: string): Promise { ? `http://${node}/api/status` : `https://${node}/api/status`; + logger.info(`Checking node (${nodeUrl}) reachability`); + try { const response = await fetch(nodeUrl); if (!response.ok) { @@ -138,7 +139,7 @@ async function checkApiReachable(node: string): Promise { } const data = await response.json(); - if (data.ApiReachable) { + if (data.ApiReachable as boolean) { logger.info(`Node ${node} is reachable.`); return true; } else { @@ -208,15 +209,14 @@ function monitorConfigFiles(): void { } async function startMasterNode() { - let isMaster: boolean = HA_MASTER == "false"; - if (isMaster) { + if (HA_MASTER == "true") { if (!HA_MASTER_IP) { logger.error( "Master's IP is not set, please set the HA_MASTER_IP variable (example: 10.0.0.4:9876)", ); } else { const haNodeConfig: HaNodeConfig = { - master: "HA_MASTER_IP", + master: HA_MASTER_IP, }; const haConfig: HighAvailabilityConfig = { active: true, @@ -226,9 +226,9 @@ async function startMasterNode() { const nodeCache: NodeCache = HA_NODE ? HA_NODE.split(",").reduce((cache, node, index) => { - const [ip, id] = node.trim().split(":"); - if (ip && id) { - cache[`node${index + 1}`] = { ip, id: parseInt(id, 10) }; + const [ip, port] = node.trim().split(":"); + if (ip && port) { + cache[`node-${index + 1}`] = { ip, id: parseInt(port, 10) }; } return cache; }, {} as NodeCache) diff --git a/src/init.ts b/src/init.ts index eb3612bf..119950c0 100644 --- a/src/init.ts +++ b/src/init.ts @@ -15,10 +15,12 @@ import { scheduleFetch } from "./controllers/scheduler"; import cors from "cors"; import { blockWhileLocked } from "./middleware/checkLock"; import logger from "./utils/logger"; +import initFiles from "./config/initFiles"; const LAB = [limiter, authMiddleware, blockWhileLocked]; const initializeApp = (app: express.Application): void => { + initFiles(); app.use(cors()); app.use(express.json()); app.use("/api-docs", (req: Request, res: Response, next: NextFunction) => From ccabc0cea19b18a936b17d195276ac2663a518f6 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 25 Dec 2024 23:38:13 +0100 Subject: [PATCH 040/135] Fix: Replacing all single file names with one folder --- .gitignore | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index ee4e7afe..6c617861 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,5 @@ # custom paths: -src/data/database.db -src/data/dockerConfig.json -src/data/highAvailability.json -src/data/states.json -src/data/user.conf -src/data/password.json -src/data/ha.lock -src/data/frontendConfiguration.json -src/data/variables.json +src/data/* docker .test* From d9c600abd8a36c696c247d45f11147cf4c8ea55a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 25 Dec 2024 23:49:09 +0100 Subject: [PATCH 041/135] Fix: Adding sample variables.json to docker container before building --- .dockerignore | 150 ++++++++++++++++++++++++++++++++++++++- Dockerfile | 1 + Dockerfile-dev | 1 + src/sample-variable.json | 22 ++++++ 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 src/sample-variable.json diff --git a/.dockerignore b/.dockerignore index 10b44aec..2d993096 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,150 @@ +# custom paths: +src/data/* +*.md *.txt -*.md \ No newline at end of file +docker +.test* +# Created by https://www.toptal.com/developers/gitignore/api/node +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/Dockerfile b/Dockerfile index 53f3b729..26f492b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,7 @@ COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ RUN npm install COPY ./src ./src +RUN mv ./src/sample-variable.json ./src/data/variables.json RUN npm run build:mini # Stage 2: main stage diff --git a/Dockerfile-dev b/Dockerfile-dev index 7ad56f09..6e9452a0 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -23,6 +23,7 @@ COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ RUN npm install COPY ./src ./src +RUN mv ./src/sample-variable.json ./src/data/variables.json RUN npm run build # Stage 2: main stage diff --git a/src/sample-variable.json b/src/sample-variable.json new file mode 100644 index 00000000..06153af5 --- /dev/null +++ b/src/sample-variable.json @@ -0,0 +1,22 @@ +{ + "VERSION": "", + "RUNNING_IN_DOCKER": "", + "TRUSTED_PROXYS": "", + "HA_MASTER": "", + "HA_MASTER_IP": "", + "HA_NODE": "", + "HA_UNSAFE": "", + "DISCORD_WEBHOOK_URL": "", + "EMAIL_SENDER": "", + "EMAIL_RECIPIENT": "", + "EMAIL_PASSWORD": "", + "EMAIL_SERVICE": "", + "PUSHBULLET_ACCESS_TOKEN": "", + "PUSHOVER_USER_KEY": "", + "PUSHOVER_API_TOKEN": "", + "SLACK_WEBHOOK_URL": "", + "TELEGRAM_BOT_TOKEN": "", + "TELEGRAM_CHAT_ID": "", + "WHATSAPP_API_URL": "", + "WHATSAPP_RECIPIENT": "" +} From c82473ecda7b2fca0e5697cd8bc0fa76e517800a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 10:16:24 +0100 Subject: [PATCH 042/135] Fix: Add error handling for malformed input data in the reduce function Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- src/utils/extractHostData.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts index b6192ea7..65577cce 100644 --- a/src/utils/extractHostData.ts +++ b/src/utils/extractHostData.ts @@ -43,13 +43,23 @@ function extractRelevantData(jsonData: JsonData) { NCPU: jsonData.info.NCPU, }, version: { - Components: jsonData.version.Components.reduce( - (acc, component) => { - acc[component.Name] = component.Version; - return acc; - }, - {}, - ), + Components: (() => { + try { + if (!Array.isArray(jsonData?.version?.Components)) { + return {}; + } + + return jsonData.version.Components.reduce((acc, component) => { + if (typeof component?.Name === 'string' && typeof component?.Version === 'string') { + acc[component.Name] = component.Version; + } + return acc; + }, {}); + } catch (error) { + console.error('Error processing Components data:', error); + return {}; + } + })(), }, }; } From 35630f46d8fe6f0c017877f928e459fdc6ef39ad Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 18:47:22 +0100 Subject: [PATCH 043/135] Fix: Add rate limiting to file read (auth) --- src/middleware/authMiddleware.ts | 3 ++- src/utils/rateLimitReadFile.ts | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/utils/rateLimitReadFile.ts diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 8caad081..a50fcda5 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -2,6 +2,7 @@ import bcrypt from "bcrypt"; import fs from "fs"; import { Request, Response, NextFunction } from "express"; import logger from "../utils/logger"; +import { rateLimitedReadFile } from "../utils/rateLimitReadFile"; const passwordFile = "./src/data/password.json"; const passwordBool = "./src/data/usePassword.txt"; @@ -28,7 +29,7 @@ async function authMiddleware( return; } - const passwordData = await fs.promises.readFile(passwordFile, "utf8"); + const passwordData = await rateLimitedReadFile(passwordFile); const storedData = JSON.parse(passwordData); const passwordMatch = await bcrypt.compare( diff --git a/src/utils/rateLimitReadFile.ts b/src/utils/rateLimitReadFile.ts new file mode 100644 index 00000000..415cddef --- /dev/null +++ b/src/utils/rateLimitReadFile.ts @@ -0,0 +1,22 @@ +import { promises as fs } from "fs"; + +const delay = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)); + +let lastReadTime = 0; +const rateLimitDuration = 500; + +export const rateLimitedReadFile = async ( + filePath: string, + encoding: BufferEncoding = "utf8", +): Promise => { + const now = Date.now(); + const timeSinceLastRead = now - lastReadTime; + + if (timeSinceLastRead < rateLimitDuration) { + await delay(rateLimitDuration - timeSinceLastRead); + } + + lastReadTime = Date.now(); + return fs.readFile(filePath, encoding); +}; From f81a96cc0bab405d4ee802ecaa4828a1689d9fdc Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 18:58:03 +0100 Subject: [PATCH 044/135] Fix: Add rate limiting FS functions --- src/middleware/authMiddleware.ts | 5 ++--- src/middleware/checkLock.ts | 8 +++---- src/utils/rateLimitFS.ts | 36 ++++++++++++++++++++++++++++++++ src/utils/rateLimitReadFile.ts | 22 ------------------- 4 files changed, 42 insertions(+), 29 deletions(-) create mode 100644 src/utils/rateLimitFS.ts delete mode 100644 src/utils/rateLimitReadFile.ts diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index a50fcda5..08ffd219 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -1,8 +1,7 @@ import bcrypt from "bcrypt"; -import fs from "fs"; import { Request, Response, NextFunction } from "express"; import logger from "../utils/logger"; -import { rateLimitedReadFile } from "../utils/rateLimitReadFile"; +import { rateLimitedReadFile } from "../utils/rateLimitFS"; const passwordFile = "./src/data/password.json"; const passwordBool = "./src/data/usePassword.txt"; @@ -13,7 +12,7 @@ async function authMiddleware( next: NextFunction, ): Promise { try { - const authStatusData = await fs.promises.readFile(passwordBool, "utf8"); + const authStatusData = await rateLimitedReadFile(passwordBool); const isAuthEnabled = authStatusData.trim() === "true"; if (!isAuthEnabled) { diff --git a/src/middleware/checkLock.ts b/src/middleware/checkLock.ts index 747889dc..73740a07 100644 --- a/src/middleware/checkLock.ts +++ b/src/middleware/checkLock.ts @@ -1,14 +1,14 @@ -import fs from "fs"; import { Request, Response, NextFunction } from "express"; +import { rateLimitedExistsSync } from "../utils/rateLimitFS"; const lockFilePath = "./src/data/ha.lock"; -export function blockWhileLocked( +export async function blockWhileLocked( req: Request, res: Response, next: NextFunction, -): void { - if (fs.existsSync(lockFilePath)) { +): Promise { + if (await rateLimitedExistsSync(lockFilePath)) { res.status(503).json({ error: "Service unavailable. The high-availability lock is currently active. Please try again later.", diff --git a/src/utils/rateLimitFS.ts b/src/utils/rateLimitFS.ts new file mode 100644 index 00000000..a8f0b42d --- /dev/null +++ b/src/utils/rateLimitFS.ts @@ -0,0 +1,36 @@ +import { promises as fs, existsSync } from "fs"; + +const delay = (ms: number): Promise => + new Promise((resolve) => setTimeout(resolve, ms)); + +let lastOperationTime = 0; +const rateLimitDuration = 500; + +export const rateLimitedReadFile = async ( + filePath: string, + encoding: BufferEncoding = "utf8", +): Promise => { + const now = Date.now(); + const timeSinceLastOperation = now - lastOperationTime; + + if (timeSinceLastOperation < rateLimitDuration) { + await delay(rateLimitDuration - timeSinceLastOperation); + } + + lastOperationTime = Date.now(); + return fs.readFile(filePath, encoding); +}; + +export const rateLimitedExistsSync = async ( + filePath: string, +): Promise => { + const now = Date.now(); + const timeSinceLastOperation = now - lastOperationTime; + + if (timeSinceLastOperation < rateLimitDuration) { + await delay(rateLimitDuration - timeSinceLastOperation); + } + + lastOperationTime = Date.now(); + return existsSync(filePath); +}; diff --git a/src/utils/rateLimitReadFile.ts b/src/utils/rateLimitReadFile.ts deleted file mode 100644 index 415cddef..00000000 --- a/src/utils/rateLimitReadFile.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { promises as fs } from "fs"; - -const delay = (ms: number): Promise => - new Promise((resolve) => setTimeout(resolve, ms)); - -let lastReadTime = 0; -const rateLimitDuration = 500; - -export const rateLimitedReadFile = async ( - filePath: string, - encoding: BufferEncoding = "utf8", -): Promise => { - const now = Date.now(); - const timeSinceLastRead = now - lastReadTime; - - if (timeSinceLastRead < rateLimitDuration) { - await delay(rateLimitDuration - timeSinceLastRead); - } - - lastReadTime = Date.now(); - return fs.readFile(filePath, encoding); -}; From 8b9493fff416963f97ed152c4514267855d8c434 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 19:16:01 +0100 Subject: [PATCH 045/135] Feat: Advanced codeql.yml --- .github/workflows/codeql.yml | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..695a6087 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,52 @@ +name: "CodeQL Advanced" + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + schedule: + - cron: '32 1 * * 5' + +jobs: + analyze: + name: Analyze TypeScript + runs-on: 'ubuntu-latest' + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From 8165b3e7ac456b6074451a237db46c0343b37b18 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 19:47:17 +0100 Subject: [PATCH 046/135] Chore: Add more workflows for validation/licensing/... --- .github/workflows/Licensed.yml | 12 + .github/workflows/remove-stale.yml | 17 + .github/workflows/validation.yml | 58 + eslint.config.mjs | 12 + package-lock.json | 1359 +++++++++++++++++++++- package.json | 20 +- src/controllers/frontendConfiguration.ts | 50 +- src/controllers/highAvailability.ts | 4 +- src/routes/getter/routes.ts | 2 +- src/utils/connectionChecker.ts | 1 - src/utils/containerService.ts | 12 +- src/utils/extractHostData.ts | 20 +- 12 files changed, 1516 insertions(+), 51 deletions(-) create mode 100644 .github/workflows/Licensed.yml create mode 100644 .github/workflows/remove-stale.yml create mode 100644 .github/workflows/validation.yml create mode 100644 eslint.config.mjs diff --git a/.github/workflows/Licensed.yml b/.github/workflows/Licensed.yml new file mode 100644 index 00000000..e6475a78 --- /dev/null +++ b/.github/workflows/Licensed.yml @@ -0,0 +1,12 @@ +name: Licensed + +on: + workflow_call: + +jobs: + validate-cached-dependency-records: + runs-on: ubuntu-latest + name: Check licenses + steps: + - name: Licensed CI + uses: github/licensed-ci@v1.11.1 diff --git a/.github/workflows/remove-stale.yml b/.github/workflows/remove-stale.yml new file mode 100644 index 00000000..ccccef97 --- /dev/null +++ b/.github/workflows/remove-stale.yml @@ -0,0 +1,17 @@ +name: "Close stale issues and PR" +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days." + stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days." + close-issue-message: "This issue was closed because it has been stalled for 5 days with no activity." + days-before-stale: 30 + days-before-close: 5 + days-before-pr-close: -1 diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 00000000..7dcf706a --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,58 @@ +name: Basic validation + +on: + workflow_call: + inputs: + operating-systems: + description: "Optional input to set a list of operating systems which the workflow uses. Defaults to ['ubuntu-latest', 'windows-latest', 'macos-latest'] if not set" + required: false + type: string + default: "['ubuntu-latest', 'windows-latest', 'macos-latest']" + enable-audit: + description: "Optional input to enable npm package audit process" + required: false + type: boolean + default: true + node-version: + description: "Optional input to set the version of Node.js used to build the project. The input syntax corresponds to the setup-node's one" + required: false + type: string + default: "20.x" + node-caching: + description: "Optional input to set up caching for the setup-node action. The input syntax corresponds to the setup-node's one. Set to an empty string if caching isn't needed" + required: false + type: string + default: "npm" + +jobs: + build: + runs-on: ${{matrix.operating-systems}} + strategy: + fail-fast: false + matrix: + operating-systems: ${{fromJson(inputs.operating-systems)}} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js ${{inputs.node-version}} + uses: actions/setup-node@v4 + with: + node-version: ${{inputs.node-version}} + cache: ${{inputs.node-caching}} + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Run prettier + run: npm run pettier + + - name: Run linter + run: npm run lint + + - name: Build + run: npm run build:mini + + - name: Audit packages + run: npm audit --audit-level=high + if: ${{inputs.enable-audit}} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..5b7b70a1 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,12 @@ +import globals from "globals"; +import pluginJs from "@eslint/js"; +import tseslint from "typescript-eslint"; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + { ignores: ["node_modules/*", "dist/*"] }, + { files: ["src/*.{ts}"] }, + { languageOptions: { globals: globals.node } }, + pluginJs.configs.recommended, + ...tseslint.configs.recommended, +]; diff --git a/package-lock.json b/package-lock.json index 27899c7b..118aa2bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "dockstatapi", - "version": "2.0.0", + "version": "2.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dockstatapi", - "version": "2.0.0", + "version": "2.0.1", "license": "BSD 3-Clause License", "dependencies": { "bcrypt": "^5.1.1", @@ -25,6 +25,7 @@ "winston": "^3.15.0" }, "devDependencies": { + "@eslint/js": "^9.17.0", "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", @@ -37,11 +38,17 @@ "@types/supports-color": "^8.1.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", "dependency-cruiser": "^16.5.0", + "eslint": "^9.17.0", + "globals": "^15.14.0", "nodemon": "^3.1.7", "ora": "^8.1.1", + "prettier": "^3.4.2", "ts-node": "^10.9.2", "tsx": "^4.19.2", + "typescript-eslint": "^8.18.2", "uglify-js": "^3.19.3" }, "engines": { @@ -539,6 +546,180 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.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" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -546,6 +727,72 @@ "license": "MIT", "optional": true }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@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, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -620,6 +867,44 @@ } } }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@npmcli/fs": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", @@ -771,6 +1056,13 @@ "@types/ssh2": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", @@ -950,6 +1242,235 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz", + "integrity": "sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/type-utils": "8.18.2", + "@typescript-eslint/utils": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz", + "integrity": "sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/typescript-estree": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz", + "integrity": "sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz", + "integrity": "sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.18.2", + "@typescript-eslint/utils": "8.18.2", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz", + "integrity": "sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz", + "integrity": "sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/visitor-keys": "8.18.2", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/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, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz", + "integrity": "sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.18.2", + "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/typescript-estree": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz", + "integrity": "sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.18.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1467,6 +1988,16 @@ "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", "license": "MIT" }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -1702,6 +2233,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -1752,6 +2298,13 @@ "node": ">=4.0.0" } }, + "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, + "license": "MIT" + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -2074,6 +2627,276 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/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, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2184,6 +3007,37 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", @@ -2191,6 +3045,16 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/fastq": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fecha": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", @@ -2220,6 +3084,19 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -2272,6 +3149,44 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==", + "dev": true, + "license": "ISC" + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -2496,6 +3411,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2515,6 +3443,13 @@ "devOptional": true, "license": "ISC" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -2683,12 +3618,29 @@ "dev": true, "license": "ISC" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, "license": "MIT", - "optional": true, "engines": { "node": ">=0.8.19" } @@ -2926,8 +3878,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "license": "ISC", - "optional": true + "devOptional": true, + "license": "ISC" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -2948,6 +3900,13 @@ "license": "MIT", "optional": true }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2955,6 +3914,13 @@ "dev": true, "license": "MIT" }, + "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2968,6 +3934,16 @@ "node": ">=6" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -2984,6 +3960,36 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -2996,6 +4002,13 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "license": "MIT" }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.mergewith": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", @@ -3155,6 +4168,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -3164,6 +4187,33 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -3375,6 +4425,13 @@ "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -3718,6 +4775,24 @@ "license": "MIT", "peer": true }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "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.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ora": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", @@ -3796,6 +4871,38 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", @@ -3812,6 +4919,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3821,6 +4941,16 @@ "node": ">= 0.8" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3830,6 +4960,16 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", @@ -3921,6 +5061,32 @@ "node": ">=10" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -3995,6 +5161,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -4010,6 +5186,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4133,6 +5330,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -4183,6 +5390,17 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4199,6 +5417,30 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/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, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4348,6 +5590,29 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", @@ -4887,6 +6152,19 @@ "node": ">= 14.0.0" } }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -5055,6 +6333,19 @@ "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", "license": "Unlicense" }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -5083,6 +6374,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.2.tgz", + "integrity": "sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.18.2", + "@typescript-eslint/parser": "8.18.2", + "@typescript-eslint/utils": "8.18.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -5139,6 +6453,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -5221,8 +6545,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "devOptional": true, "license": "ISC", - "optional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -5278,6 +6602,16 @@ "node": ">= 12.0.0" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5309,6 +6643,19 @@ "node": ">=6" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/z-schema": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", diff --git a/package.json b/package.json index eb9f8652..20af78a8 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,10 @@ "docker": "docker compose up -d", "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", "docker:build": "docker build . -t \"dockstatapi:local\" -f ./Dockerfile-dev && docker compose up -d", - "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down" + "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", + "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write", + "lint": "npx eslint", + "lint:fix": "npx eslint --fix" }, "keywords": [], "author": "Its4Nik", @@ -39,23 +42,30 @@ "winston": "^3.15.0" }, "devDependencies": { - "@types/dockerode": "^3.3.31", - "@types/supports-color": "^8.1.3", - "@types/swagger-jsdoc": "^6.0.4", - "@types/swagger-ui-express": "^4.1.7", + "@eslint/js": "^9.17.0", "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", + "@types/dockerode": "^3.3.31", "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", "@types/node": "^22.9.0", "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", + "@types/supports-color": "^8.1.3", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^8.18.2", + "@typescript-eslint/parser": "^8.18.2", "dependency-cruiser": "^16.5.0", + "eslint": "^9.17.0", + "globals": "^15.14.0", "nodemon": "^3.1.7", "ora": "^8.1.1", + "prettier": "^3.4.2", "ts-node": "^10.9.2", "tsx": "^4.19.2", + "typescript-eslint": "^8.18.2", "uglify-js": "^3.19.3" }, "engines": { diff --git a/src/controllers/frontendConfiguration.ts b/src/controllers/frontendConfiguration.ts index e83eaaee..4d31943e 100644 --- a/src/controllers/frontendConfiguration.ts +++ b/src/controllers/frontendConfiguration.ts @@ -9,7 +9,7 @@ const regex = new RegExp(expression); // Hide Containers: async function hideContainer(containerName: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -29,7 +29,7 @@ async function hideContainer(containerName: string) { async function unhideContainer(containerName: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -49,7 +49,7 @@ async function unhideContainer(containerName: string) { // Tag containers async function addTagToContainer(containerName: string, tag: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -72,7 +72,7 @@ async function addTagToContainer(containerName: string, tag: string) { async function removeTagFromContainer(containerName: string, tag: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -94,7 +94,7 @@ async function removeTagFromContainer(containerName: string, tag: string) { // Pin containers async function pinContainer(containerName: string) { try { - let data: any = await readData(); + const data: any = await readData(); const containerIndex: number = data.findIndex( (container: any) => container.name === containerName, ); @@ -114,7 +114,7 @@ async function pinContainer(containerName: string) { async function unpinContainer(containerName: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -135,7 +135,7 @@ async function unpinContainer(containerName: string) { async function setLink(containerName: string, link: string) { if (link.match(regex)) { try { - let data: any = await readData(); + const data: any = await readData(); const containerIndex: any = data.findIndex( (container: any) => container.name === containerName, ); @@ -159,7 +159,7 @@ async function setLink(containerName: string, link: string) { async function removeLink(containerName: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); @@ -179,28 +179,26 @@ async function removeLink(containerName: string) { // Add/remove icon from containers async function setIcon(containerName: string, icon: string, custom: boolean) { try { - let data = await readData(); + const data = await readData(); const containerIndex: number = data.findIndex( (container: any) => container.name === containerName, ); if (custom === true) { - if (containerIndex !== -1) { - data[containerIndex].icon = `custom/${icon}`; - await saveData(data); - } else { - data.push({ name: containerName, icon: `custom/${icon}` }); - await saveData(data); - } - } - else if (containerIndex !== -1) { - data[containerIndex].icon = `${icon}`; - await saveData(data); - } - else { - data.push({ name: containerName, icon: `${icon}` }); - await saveData(data); - } + if (containerIndex !== -1) { + data[containerIndex].icon = `custom/${icon}`; + await saveData(data); + } else { + data.push({ name: containerName, icon: `custom/${icon}` }); + await saveData(data); + } + } else if (containerIndex !== -1) { + data[containerIndex].icon = `${icon}`; + await saveData(data); + } else { + data.push({ name: containerName, icon: `${icon}` }); + await saveData(data); + } } catch (error: any) { logger.error(error); throw new Error(error); @@ -209,7 +207,7 @@ async function setIcon(containerName: string, icon: string, custom: boolean) { async function removeIcon(containerName: string) { try { - let data = await readData(); + const data = await readData(); const containerIndex = data.findIndex( (container: any) => container.name === containerName, ); diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index 919148e4..dd16bf6c 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -124,7 +124,7 @@ async function prepareFilesForSync(): Promise> { } async function checkApiReachable(node: string): Promise { - let nodeUrl = + const nodeUrl = useUnsafeConnection === true ? `http://${node}/api/status` : `https://${node}/api/status`; @@ -170,7 +170,7 @@ async function synchronizeFilesWithNodes(): Promise { continue; // Skip synchronization if the node is unreachable } - let nodeUrl = + const nodeUrl = useUnsafeConnection == true ? `http://${node}/ha/sync` : `https://${node}/ha/sync`; diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index b6c89c12..8e3c6955 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -332,7 +332,7 @@ router.get("/current-schedule", (req: Request, res: Response) => { router.get("/status", async (req: Request, res: Response) => { logger.debug("Fetching /api/status"); try { - let jsonData = await checkReachability(); + const jsonData = await checkReachability(); res.status(200).json(jsonData); } catch (error: any) { logger.error(`Error while fetching data: ${error}`); diff --git a/src/utils/connectionChecker.ts b/src/utils/connectionChecker.ts index c61ffebd..289b9b37 100644 --- a/src/utils/connectionChecker.ts +++ b/src/utils/connectionChecker.ts @@ -67,7 +67,6 @@ async function checkReachability(): Promise { const parsedData = JSON.parse(data); const hosts: Host[] = parsedData.hosts; return await checkHostStatus(hosts); - } catch (error: any) { logger.error(`Error reading file: ${error}`); return undefined; diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts index afc035a1..0cd09e39 100644 --- a/src/utils/containerService.ts +++ b/src/utils/containerService.ts @@ -31,8 +31,14 @@ interface AllContainerData { function loadConfig() { try { if (!fs.existsSync(configPath)) { - logger.warn(`Config file not found. Creating an empty file at ${configPath}`); - fs.writeFileSync(configPath, JSON.stringify({ "hosts": [] }, null, 2), "utf-8"); + logger.warn( + `Config file not found. Creating an empty file at ${configPath}`, + ); + fs.writeFileSync( + configPath, + JSON.stringify({ hosts: [] }, null, 2), + "utf-8", + ); } const configData = fs.readFileSync(configPath, "utf-8"); @@ -80,7 +86,7 @@ async function fetchAllContainers(): Promise { const cpuUsage = systemCpuDelta > 0 ? (cpuDelta / systemCpuDelta) * - containerStats.cpu_stats.online_cpus + containerStats.cpu_stats.online_cpus : 0; return { diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts index 65577cce..25ea0168 100644 --- a/src/utils/extractHostData.ts +++ b/src/utils/extractHostData.ts @@ -49,14 +49,20 @@ function extractRelevantData(jsonData: JsonData) { return {}; } - return jsonData.version.Components.reduce((acc, component) => { - if (typeof component?.Name === 'string' && typeof component?.Version === 'string') { - acc[component.Name] = component.Version; - } - return acc; - }, {}); + return jsonData.version.Components.reduce( + (acc, component) => { + if ( + typeof component?.Name === "string" && + typeof component?.Version === "string" + ) { + acc[component.Name] = component.Version; + } + return acc; + }, + {}, + ); } catch (error) { - console.error('Error processing Components data:', error); + console.error("Error processing Components data:", error); return {}; } })(), From 0dd818e3d210d7ffd4fcacf14c3e8f0477f23882 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 19:55:37 +0100 Subject: [PATCH 047/135] Fix: Update Licensed.yml --- .github/workflows/Licensed.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/Licensed.yml b/.github/workflows/Licensed.yml index e6475a78..192ec8f0 100644 --- a/.github/workflows/Licensed.yml +++ b/.github/workflows/Licensed.yml @@ -1,8 +1,9 @@ name: Licensed -on: - workflow_call: - +on: + push: + branches: '**' + jobs: validate-cached-dependency-records: runs-on: ubuntu-latest From d2d65c627b8bb6344052432792d496b0b4db5b0d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:02:40 +0100 Subject: [PATCH 048/135] Fix: Adjusted workflows --- .github/workflows/Licensed.yml | 23 +++++++++++++----- .github/workflows/anchore.yml | 12 ---------- .github/workflows/cloc.yaml | 24 +++++++++---------- .github/workflows/test-build.yaml | 5 +--- .github/workflows/validation.yml | 39 ++++--------------------------- 5 files changed, 35 insertions(+), 68 deletions(-) diff --git a/.github/workflows/Licensed.yml b/.github/workflows/Licensed.yml index e6475a78..b81989fb 100644 --- a/.github/workflows/Licensed.yml +++ b/.github/workflows/Licensed.yml @@ -1,12 +1,23 @@ name: Licensed -on: - workflow_call: +on: [push] jobs: - validate-cached-dependency-records: + license-check: runs-on: ubuntu-latest - name: Check licenses steps: - - name: Licensed CI - uses: github/licensed-ci@v1.11.1 + - name: Checkout latest code + uses: actions/checkout@v4 + - name: Use Node.js 20.x + uses: actions/setup-node@latest + with: + node-version: 20.x + - name: Run npm install + run: npm install + - name: Check licenses + uses: tangro/actions-license-check@v1.0.14 + with: + allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_CONTEXT: ${{ toJson(github) }} diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index bb5df127..23c78abc 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -1,21 +1,9 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. - -# This workflow checks out code, builds an image, performs a container image -# vulnerability scan with Anchore's Grype tool, and integrates the results with GitHub Advanced Security -# code scanning feature. For more information on the Anchore scan action usage -# and parameters, see https://github.com/anchore/scan-action. For more -# information on Anchore's container image scanning tool Grype, see -# https://github.com/anchore/grype name: Anchore Grype vulnerability scan on: push: branches: ["main"] pull_request: - # The branches below must be a subset of the branches above branches: ["main", "dev"] schedule: - cron: "30 9 * * 1" diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index 9ce7e271..795ad75d 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -6,23 +6,23 @@ permissions: on: push: - branches: [ main ] + branches: [main, dev] pull_request: - branches: [ main, dev ] + branches: [main, dev] jobs: cloc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 - - name: Count Lines of Code (cloc) - uses: djdefi/cloc-action@6 - with: - options: --md --report-file=cloc.md --exclude-dir=node_modules --exclude-lang=YAML,JSON --exclude-list-file=package-lock.json - - - name: Create comment from markdown file - uses: GrantBirki/comment@v2.1.0 - with: - file: cloc.md \ No newline at end of file + - name: Count Lines of Code (cloc) + uses: djdefi/cloc-action@6 + with: + options: --md --report-file=cloc.md --exclude-dir=node_modules --exclude-lang=YAML,JSON --exclude-list-file=package-lock.json + + - name: Create comment from markdown file + uses: GrantBirki/comment@v2.1.0 + with: + file: cloc.md diff --git a/.github/workflows/test-build.yaml b/.github/workflows/test-build.yaml index 8c805d46..0b304ec2 100644 --- a/.github/workflows/test-build.yaml +++ b/.github/workflows/test-build.yaml @@ -1,9 +1,6 @@ name: Test building -on: - pull_request: - branches: - - "dev" +on: [push] permissions: packages: write diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 7dcf706a..c5150a15 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -1,45 +1,17 @@ -name: Basic validation - -on: - workflow_call: - inputs: - operating-systems: - description: "Optional input to set a list of operating systems which the workflow uses. Defaults to ['ubuntu-latest', 'windows-latest', 'macos-latest'] if not set" - required: false - type: string - default: "['ubuntu-latest', 'windows-latest', 'macos-latest']" - enable-audit: - description: "Optional input to enable npm package audit process" - required: false - type: boolean - default: true - node-version: - description: "Optional input to set the version of Node.js used to build the project. The input syntax corresponds to the setup-node's one" - required: false - type: string - default: "20.x" - node-caching: - description: "Optional input to set up caching for the setup-node action. The input syntax corresponds to the setup-node's one. Set to an empty string if caching isn't needed" - required: false - type: string - default: "npm" +on: [push] jobs: build: - runs-on: ${{matrix.operating-systems}} - strategy: - fail-fast: false - matrix: - operating-systems: ${{fromJson(inputs.operating-systems)}} + runs-on: ubuntu steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node.js ${{inputs.node-version}} + - name: Setup Node.js 20 uses: actions/setup-node@v4 with: - node-version: ${{inputs.node-version}} - cache: ${{inputs.node-caching}} + node-version: 20 + cache: npm - name: Install dependencies run: npm ci --ignore-scripts @@ -55,4 +27,3 @@ jobs: - name: Audit packages run: npm audit --audit-level=high - if: ${{inputs.enable-audit}} From 575d8e594173c7a04b6828fd759a9e9640b8aeae Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:32:28 +0100 Subject: [PATCH 049/135] Fix: Adjusted workflows; fixed versions --- .github/workflows/anchore.yml | 32 ++++------------- .github/workflows/build-dev.yaml | 10 +++--- .github/workflows/build-image.yml | 12 +++---- .github/workflows/build-test.yaml | 56 ++++++++++++++++++++++++++++++ .github/workflows/cloc.yaml | 6 ++-- .github/workflows/codeql.yml | 6 ++-- .github/workflows/licensed.yml | 23 ++++++++++++ .github/workflows/remove-stale.yml | 2 +- .github/workflows/validation.yml | 2 +- 9 files changed, 105 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/build-test.yaml create mode 100644 .github/workflows/licensed.yml diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 715be200..bafb5cc6 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -1,36 +1,18 @@ name: Anchore Grype vulnerability scan -on: - push: - branches: ["main"] - pull_request: - branches: ["main", "dev"] - schedule: - - cron: "30 9 * * 1" - -permissions: - contents: read - +on: [push] jobs: - Anchore-Build-Scan: - permissions: - contents: read - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + build: runs-on: ubuntu-latest steps: - - name: Check out the code - uses: actions/checkout@4 - - name: Build the Docker image + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - name: Run the Anchore Grype scan action - uses: anchore/scan-action@d5aa5b6cb9414b0c7771438046ff5bcfa2854ed7 + - uses: anchore/scan-action@v3 id: scan with: image: "localbuild/testimage:latest" - fail-build: true - severity-cutoff: critical - - name: Upload vulnerability report - uses: github/codeql-action/upload-sarif + - name: upload Anchore scan SARIF report + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ${{ steps.scan.outputs.sarif }} diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index f08ba20b..b81287c2 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -14,20 +14,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action + uses: docker/setup-buildx-action@v3 - name: Login to Github Container Registry - uses: docker/login-action + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ github.token }} - name: Generate Docker tags - uses: docker/metadata-action + uses: docker/metadata-action@v5 id: metadata with: images: ghcr.io/${{ github.repository }} @@ -37,7 +37,7 @@ jobs: latest=false - name: Build and push - uses: docker/build-push-action + uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64, push: true diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 8c58d700..d7d131e3 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,4 +1,4 @@ -name: Buiod dockstatapi:latest +name: Build dockstatapi:latest on: release: @@ -13,20 +13,20 @@ jobs: runs-on: ubuntu-latest steps: - name: Set up QEMU - uses: docker/setup-qemu-action + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action + uses: docker/setup-buildx-action@v3 - name: Login to Github Container Registry - uses: docker/login-action + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ github.token }} - name: Generate Docker tags - uses: docker/metadata-action + uses: docker/metadata-action@v5 id: metadata with: images: ghcr.io/${{ github.repository }} @@ -36,7 +36,7 @@ jobs: latest=true - name: Build and push - uses: docker/build-push-action + uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 push: true diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml new file mode 100644 index 00000000..8b0af360 --- /dev/null +++ b/.github/workflows/build-test.yaml @@ -0,0 +1,56 @@ +name: Build test docker image + +on: [push] + +permissions: + packages: write + contents: read + +jobs: + build-main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@4 + + # Set up Node.js using nvm + - name: Set up Node.js version from .nvmrc + run: | + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + nvm install + nvm use + node -v + npm -v + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} + + - name: Build and Push Docker Images + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + push: false + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index 4c887638..052ba123 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -15,14 +15,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@4 + - uses: actions/checkout@v4 - name: Count Lines of Code (cloc) - uses: djdefi/cloc-action + uses: djdefi/cloc-action@6 with: options: --md --report-file=cloc.md --exclude-dir=node_modules --exclude-lang=YAML,JSON --exclude-list-file=package-lock.json - name: Create comment from markdown file - uses: GrantBirki/comment + uses: GrantBirki/comment@v2 with: file: cloc.md diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3a14993c..b1af0a79 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,10 +27,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@4 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -47,6 +47,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/licensed.yml b/.github/workflows/licensed.yml new file mode 100644 index 00000000..f384e7ae --- /dev/null +++ b/.github/workflows/licensed.yml @@ -0,0 +1,23 @@ +name: License checker + +on: [push] + +jobs: + license-check: + runs-on: ubuntu-latest + steps: + - name: Checkout latest code + uses: actions/checkout@4 + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + - name: Run npm install + run: npm install + - name: Check licenses + uses: tangro/actions-license-check@v1 + with: + allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_CONTEXT: ${{ toJson(github) }} diff --git a/.github/workflows/remove-stale.yml b/.github/workflows/remove-stale.yml index 2285ecb6..ccccef97 100644 --- a/.github/workflows/remove-stale.yml +++ b/.github/workflows/remove-stale.yml @@ -7,7 +7,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale + - uses: actions/stale@v9 with: stale-issue-message: "This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days." stale-pr-message: "This PR is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 10 days." diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 7402102c..20bc3ef6 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -8,7 +8,7 @@ jobs: uses: actions/checkout@4 - name: Setup Node.js 20 - uses: actions/setup-node + uses: actions/setup-node@v4 with: node-version: 20 cache: npm From 491171d78df3f297537dfae3c6ecb4bbc5b5fd70 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:42:56 +0100 Subject: [PATCH 050/135] Fix: Correct workflow versions --- .github/workflows/anchore.yml | 1 + .github/workflows/build-test.yaml | 2 +- .github/workflows/cloc.yaml | 1 + .github/workflows/license.yml | 23 +++++++++++++++++++++++ .github/workflows/validation.yml | 6 ++++-- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/license.yml diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index bafb5cc6..22632af8 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -12,6 +12,7 @@ jobs: id: scan with: image: "localbuild/testimage:latest" + fail-build: false - name: upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 8b0af360..f25efed2 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@4 + uses: actions/checkout@v4 # Set up Node.js using nvm - name: Set up Node.js version from .nvmrc diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index 052ba123..9ea30547 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -26,3 +26,4 @@ jobs: uses: GrantBirki/comment@v2 with: file: cloc.md + issue-number: ${{ github.event.number }} diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml new file mode 100644 index 00000000..495314c0 --- /dev/null +++ b/.github/workflows/license.yml @@ -0,0 +1,23 @@ +name: License checker + +on: [push] + +jobs: + license-check: + runs-on: ubuntu-latest + steps: + - name: Checkout latest code + uses: actions/checkout@v4 + - name: Use Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + - name: Run npm install + run: npm install + - name: Check licenses + uses: tangro/actions-license-check@1 + with: + allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_CONTEXT: ${{ toJson(github) }} diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 20bc3ef6..3183f54a 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -1,11 +1,13 @@ +name: "Run all tests" + on: [push] jobs: build: - runs-on: ubuntu + runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@4 + uses: actions/checkout@v4 - name: Setup Node.js 20 uses: actions/setup-node@v4 From f4c1c5e0876cefd5541b78e10ffb5f70e197d26d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:48:34 +0100 Subject: [PATCH 051/135] Fix: Workflow stuff... --- .github/workflows/cloc.yaml | 2 -- .github/workflows/license.yml | 2 +- .github/workflows/validation.yml | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index 9ea30547..d29afa4a 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -5,8 +5,6 @@ permissions: pull-requests: write on: - push: - branches: [main, dev] pull_request: branches: [main, dev] diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml index 495314c0..3089a541 100644 --- a/.github/workflows/license.yml +++ b/.github/workflows/license.yml @@ -15,7 +15,7 @@ jobs: - name: Run npm install run: npm install - name: Check licenses - uses: tangro/actions-license-check@1 + uses: tangro/actions-license-check@v1.0.14 with: allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" env: diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 3183f54a..7b24cf87 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -19,7 +19,7 @@ jobs: run: npm ci --ignore-scripts - name: Run prettier - run: npm run pettier + run: npm run prettier - name: Run linter run: npm run lint From 7ab3ef25210004515842a99e662e3069cc22b261 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:55:12 +0100 Subject: [PATCH 052/135] Fix: Dropping licence check (will be back) Fix: added sarif file for anchore --- .github/workflows/anchore.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 22632af8..57a7fb4a 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -13,7 +13,8 @@ jobs: with: image: "localbuild/testimage:latest" fail-build: false + output-file: ./result.sarif - name: upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: ${{ steps.scan.outputs.sarif }} + sarif_file: ./result.sarif From d53e0faab41ff40a428b6b097111f9762d33be43 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:57:10 +0100 Subject: [PATCH 053/135] Fix: Delete .github/workflows/Licensed.yml Files were deleted on my laptop but not on GH --- .github/workflows/Licensed.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/Licensed.yml diff --git a/.github/workflows/Licensed.yml b/.github/workflows/Licensed.yml deleted file mode 100644 index 557e7bb0..00000000 --- a/.github/workflows/Licensed.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Licensed - -on: [push] - -jobs: - license-check: - runs-on: ubuntu-latest - steps: - - name: Checkout latest code - uses: actions/checkout@4 - - name: Use Node.js 20.x - uses: actions/setup-node - with: - node-version: 20.x - - name: Run npm install - run: npm install - - name: Check licenses - uses: tangro/actions-license-check - with: - allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_CONTEXT: ${{ toJson(github) }} From aa38d1b38d9876d56e2dfd0a7a3152b6b4151d43 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:57:32 +0100 Subject: [PATCH 054/135] Fix: Delete .github/workflows/license.yml Files were deleted on my laptop but not on GH --- .github/workflows/license.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/license.yml diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml deleted file mode 100644 index 3089a541..00000000 --- a/.github/workflows/license.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: License checker - -on: [push] - -jobs: - license-check: - runs-on: ubuntu-latest - steps: - - name: Checkout latest code - uses: actions/checkout@v4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: 20.x - - name: Run npm install - run: npm install - - name: Check licenses - uses: tangro/actions-license-check@v1.0.14 - with: - allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_CONTEXT: ${{ toJson(github) }} From 66f6c60270ab15e9c0604fe0a649b0c456f6273e Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:57:56 +0100 Subject: [PATCH 055/135] Fix: Renamed .github/workflows/test-build.yaml --- .github/workflows/test-build.yaml | 56 ------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 .github/workflows/test-build.yaml diff --git a/.github/workflows/test-build.yaml b/.github/workflows/test-build.yaml deleted file mode 100644 index d298b76b..00000000 --- a/.github/workflows/test-build.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: Test building - -on: [push] - -permissions: - packages: write - contents: read - -jobs: - build-main: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@4 - - # Set up Node.js using nvm - - name: Set up Node.js version from .nvmrc - run: | - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash - export NVM_DIR="$HOME/.nvm" - [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - nvm install - nvm use - node -v - npm -v - - - name: Set up QEMU - uses: docker/setup-qemu-action - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action - - - name: Login to Github Container Registry - uses: docker/login-action - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate Docker tags - uses: docker/metadata-action - id: metadata - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} - - - name: Build and Push Docker Images - uses: docker/build-push-action - with: - platforms: linux/amd64,linux/arm64 - push: false - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max From 93c392c852d1298d55595654396830de059006ed Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 22:58:17 +0100 Subject: [PATCH 056/135] Fix: Delete .github/workflows/licensed.yml Files were deleted on my laptop but not on GH --- .github/workflows/licensed.yml | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 .github/workflows/licensed.yml diff --git a/.github/workflows/licensed.yml b/.github/workflows/licensed.yml deleted file mode 100644 index f384e7ae..00000000 --- a/.github/workflows/licensed.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: License checker - -on: [push] - -jobs: - license-check: - runs-on: ubuntu-latest - steps: - - name: Checkout latest code - uses: actions/checkout@4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 - with: - node-version: 20.x - - name: Run npm install - run: npm install - - name: Check licenses - uses: tangro/actions-license-check@v1 - with: - allowed-licenses: "MIT; ISC; Apache-2.0; Custom: https://www.telerik.com/kendo-angular-ui/; Custom: https://www.telerik.com/kendo-react-ui/; BSD" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_CONTEXT: ${{ toJson(github) }} From 7be77f78556398f10680ea8267ea2a51fc137786 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 26 Dec 2024 23:07:46 +0100 Subject: [PATCH 057/135] Chore: Cleaning up (gn) --- .github/workflows/anchore.yml | 5 ++--- .github/workflows/build-dev.yaml | 2 +- .github/workflows/build-image.yml | 2 +- .github/workflows/build-test.yaml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/remove-stale.yml | 2 +- .github/workflows/validation.yml | 2 +- README.md | 3 ++- TODO.md | 20 ++++++++++---------- package.json | 2 +- 10 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 57a7fb4a..84eac32e 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -1,6 +1,7 @@ name: Anchore Grype vulnerability scan on: [push] + jobs: build: runs-on: ubuntu-latest @@ -12,9 +13,7 @@ jobs: id: scan with: image: "localbuild/testimage:latest" - fail-build: false - output-file: ./result.sarif - name: upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: ./result.sarif + sarif_file: ${{ steps.scan.outputs.sarif }} diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index b81287c2..f21ab4ac 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -10,7 +10,7 @@ permissions: contents: read jobs: - build-main: + build-dev: runs-on: ubuntu-latest steps: - name: Set up QEMU diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index d7d131e3..17933f97 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -9,7 +9,7 @@ permissions: contents: read jobs: - build-main: + build-release: runs-on: ubuntu-latest steps: - name: Set up QEMU diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index f25efed2..2f2322f5 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -7,7 +7,7 @@ permissions: contents: read jobs: - build-main: + build-test: runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b1af0a79..081205c6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -9,7 +9,7 @@ on: - cron: "32 1 * * 5" jobs: - analyze: + codeql: name: Analyze TypeScript runs-on: "ubuntu-latest" permissions: diff --git a/.github/workflows/remove-stale.yml b/.github/workflows/remove-stale.yml index ccccef97..93d1acdc 100644 --- a/.github/workflows/remove-stale.yml +++ b/.github/workflows/remove-stale.yml @@ -4,7 +4,7 @@ on: - cron: "30 1 * * *" jobs: - stale: + remove-stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v9 diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index 7b24cf87..d46610bc 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -3,7 +3,7 @@ name: "Run all tests" on: [push] jobs: - build: + validation: runs-on: ubuntu-latest steps: - name: Checkout diff --git a/README.md b/README.md index f602f3b2..50246a18 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # DockStatAPI v2 + ![Dockstat Logo](.github/DockStat.png) -*Pipelines:*
+_Pipelines:_
[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml)
[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml)
diff --git a/TODO.md b/TODO.md index c4687d72..36d32653 100644 --- a/TODO.md +++ b/TODO.md @@ -1,13 +1,13 @@ -- [X] ~Better Offline mode using "faker" library or self written (probably self written)~ Not needed since there is a docker-compsoe file for local testing integrated inside the repo -- [X] HA compatibility -- [X] !!! Needs testing !!! Add automatic notifications when container state changes, according to selected level for notification service +- [x] ~Better Offline mode using "faker" library or self written (probably self written)~ Not needed since there is a docker-compsoe file for local testing integrated inside the repo +- [x] HA compatibility +- [x] !!! Needs testing !!! Add automatic notifications when container state changes, according to selected level for notification service - [ ] Image update and update notifications - [ ] trigger container restart / stop / start via backend routes -- [X] Add more logging -- [X] Structure code differently -- [X] Write new README and make the docs better -- [X] Update more files to correct TS syntax => remove "any" +- [x] Add more logging +- [x] Structure code differently +- [x] Write new README and make the docs better +- [x] Update more files to correct TS syntax => remove "any" - [ ] Websockets -- [X] Better /api/status endpoint with connection status of each host -- [X] Update notification service -- [X] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) +- [x] Better /api/status endpoint with connection status of each host +- [x] Update notification service +- [x] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) diff --git a/package.json b/package.json index 20af78a8..fa0e693f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", "docker:build": "docker build . -t \"dockstatapi:local\" -f ./Dockerfile-dev && docker compose up -d", "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", - "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write", + "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.{yaml,yml} --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", "lint": "npx eslint", "lint:fix": "npx eslint --fix" }, From 7c669ea2d369b523f13079b3b21532e12a5806cc Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:12:02 +0100 Subject: [PATCH 058/135] Fix: Test new grype workflow --- .github/workflows/anchore.yml | 10 +- Dockerfile | 4 +- Dockerfile-dev | 4 +- package-lock.json | 771 +++++++++++++++++++--------------- 4 files changed, 452 insertions(+), 337 deletions(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 84eac32e..4c8ad741 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -6,14 +6,14 @@ jobs: build: runs-on: ubuntu-latest steps: + - name: Download Grype + run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $GITHUB_PATH - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - uses: anchore/scan-action@v3 - id: scan - with: - image: "localbuild/testimage:latest" + - name: Run Grype test + run: grype -o sarif localbuild/testimage:latest > results.sarif - name: upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: - sarif_file: ${{ steps.scan.outputs.sarif }} + sarif_file: ./results.sarif diff --git a/Dockerfile b/Dockerfile index 26f492b5..dc4f58cf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN apk update && \ apk add bash -COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install COPY ./src ./src @@ -38,7 +38,7 @@ WORKDIR /build RUN mkdir -p /build/src/data -COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install --omit=dev COPY --from=builder /build/dist/* /build/src diff --git a/Dockerfile-dev b/Dockerfile-dev index 6e9452a0..bd246884 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -19,7 +19,7 @@ RUN apk update && \ apk add bash -COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install COPY ./src ./src @@ -38,7 +38,7 @@ WORKDIR /build RUN mkdir -p /build/src/data -COPY tsconfig.json environment.d.ts package*.json tsconfig.json yarn.lock ./ +COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install --omit=dev COPY --from=builder /build/dist/* /build/src diff --git a/package-lock.json b/package-lock.json index 118aa2bc..847bd243 100644 --- a/package-lock.json +++ b/package-lock.json @@ -590,6 +590,30 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/@eslint/core": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", @@ -644,6 +668,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/@eslint/eslintrc/node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -657,16 +692,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -674,17 +699,17 @@ "dev": true, "license": "MIT" }, - "node_modules/@eslint/eslintrc/node_modules/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==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "*" } }, "node_modules/@eslint/js": { @@ -932,13 +957,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", - "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", + "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.0" + "playwright": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -1117,9 +1142,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1195,9 +1220,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.67", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz", - "integrity": "sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ==", + "version": "18.19.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", + "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", "dev": true, "license": "MIT", "dependencies": { @@ -1272,16 +1297,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { "version": "8.18.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz", @@ -1390,32 +1405,6 @@ "typescript": ">=4.8.4 <5.8.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/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, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@typescript-eslint/utils": { "version": "8.18.2", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz", @@ -1627,26 +1616,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansi-styles/node_modules/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ansi-styles/node_modules/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==", - "dev": true, - "license": "MIT" - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -1857,13 +1826,13 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1951,35 +1920,33 @@ "node": ">= 10" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", - "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/call-me-maybe": { @@ -1999,22 +1966,26 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, "node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2085,18 +2056,22 @@ } }, "node_modules/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==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/color-string": { @@ -2118,6 +2093,21 @@ "color-support": "bin.js" } }, + "node_modules/color/node_modules/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==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, "node_modules/colorspace": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", @@ -2305,23 +2295,6 @@ "dev": true, "license": "MIT" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2348,9 +2321,9 @@ } }, "node_modules/dependency-cruiser": { - "version": "16.7.0", - "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.7.0.tgz", - "integrity": "sha512-522LLjHINl9r0RIZ8/6s6TqIHTuEJG3XDU2WPSm9dG0rvLUYVyQwE9ID31tDFs4OOyEhdOPaqAaAG1jRv/Zwbg==", + "version": "16.8.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.8.0.tgz", + "integrity": "sha512-VyBzIrLHfG7rT36URln+CTy8VSjrLB7YDlMx5vtBSHRHCOXgLUCcP4n5ZoD+s166T0i5LN33q1CvBkEOGsDTSg==", "dev": true, "license": "MIT", "dependencies": { @@ -2375,7 +2348,7 @@ "semver": "^7.6.3", "teamcity-service-messages": "^0.1.14", "tsconfig-paths-webpack-plugin": "^4.2.0", - "watskeburt": "^4.1.1" + "watskeburt": "^4.2.2" }, "bin": { "depcruise": "bin/dependency-cruise.mjs", @@ -2389,6 +2362,16 @@ "node": "^18.17||>=20" } }, + "node_modules/dependency-cruiser/node_modules/ignore": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", + "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -2460,12 +2443,12 @@ } }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -2533,9 +2516,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2581,6 +2564,18 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", @@ -2747,21 +2742,15 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { @@ -2777,39 +2766,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/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, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -2817,17 +2773,17 @@ "dev": true, "license": "MIT" }, - "node_modules/eslint/node_modules/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==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=8" + "node": "*" } }, "node_modules/espree": { @@ -2971,9 +2927,9 @@ } }, "node_modules/express-rate-limit": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz", - "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==", + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", "license": "MIT", "engines": { "node": ">= 16" @@ -2982,7 +2938,7 @@ "url": "https://github.com/sponsors/express-rate-limit" }, "peerDependencies": { - "express": "4 || 5 || ^5.0.0-beta.1" + "express": "^4.11 || 5 || ^5.0.0-beta.1" } }, "node_modules/express/node_modules/debug": { @@ -3024,6 +2980,19 @@ "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/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", @@ -3321,19 +3290,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz", - "integrity": "sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "gopd": "^1.2.0", "has-symbols": "^1.1.0", - "hasown": "^2.0.2" + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -3383,16 +3354,38 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">= 6" + "node": "*" } }, "node_modules/global-directory": { @@ -3451,25 +3444,13 @@ "license": "MIT" }, "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/has-symbols": { @@ -3602,9 +3583,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -3742,9 +3723,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { @@ -4032,6 +4013,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/log-symbols/node_modules/is-unicode-supported": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", @@ -4134,6 +4128,15 @@ "node": ">= 10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4273,15 +4276,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -4584,9 +4591,9 @@ } }, "node_modules/nodemon": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", - "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", "dev": true, "license": "MIT", "dependencies": { @@ -4612,6 +4619,17 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/nodemon/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -4637,6 +4655,42 @@ "fsevents": "~2.3.2" } }, + "node_modules/nodemon/node_modules/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nodemon/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -4663,6 +4717,19 @@ "node": ">=8.10.0" } }, + "node_modules/nodemon/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -4830,6 +4897,19 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, + "node_modules/ora/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ora/node_modules/emoji-regex": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", @@ -5004,13 +5084,13 @@ } }, "node_modules/playwright": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", - "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", + "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.49.0" + "playwright-core": "1.49.1" }, "bin": { "playwright": "cli.js" @@ -5023,9 +5103,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", - "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "version": "1.49.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", + "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5252,6 +5332,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/rc/node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5313,19 +5402,22 @@ } }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5567,23 +5659,6 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -5614,15 +5689,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -5902,25 +6031,29 @@ } }, "node_modules/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==", + "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, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "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, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -5956,6 +6089,16 @@ "node": ">=12.0.0" } }, + "node_modules/swagger-jsdoc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/swagger-jsdoc/node_modules/commander": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", @@ -5986,6 +6129,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/swagger-jsdoc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/swagger-parser": { "version": "10.0.3", "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", @@ -6240,46 +6395,6 @@ "node": ">=10.13.0" } }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/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, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/tsconfig-paths-webpack-plugin/node_modules/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, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", From 1cc4073596dce42cd668c2c4928fa5d6dcaa815d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:13:06 +0100 Subject: [PATCH 059/135] Fix: Test new grype workflow --- .github/workflows/anchore.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 4c8ad741..00dd53c1 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Download Grype - run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $GITHUB_PATH + run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $GITHUB_PATH/grype - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest From a44ac5f3598d307e7442a6b1681a3ecbc4f6f4b8 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:14:59 +0100 Subject: [PATCH 060/135] Fix: Test new grype workflow --- .github/workflows/anchore.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 00dd53c1..02ad1658 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -6,14 +6,17 @@ jobs: build: runs-on: ubuntu-latest steps: + - name: Set up Grype installation path + run: echo "$HOME/bin" >> $GITHUB_PATH - name: Download Grype - run: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $GITHUB_PATH/grype - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin + - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest - name: Run Grype test run: grype -o sarif localbuild/testimage:latest > results.sarif - - name: upload Anchore scan SARIF report + - name: Upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ./results.sarif From 9b0037899b1ff1f688fade50bdf669ab650daf44 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:17:30 +0100 Subject: [PATCH 061/135] Fix: Test new grype workflow --- .github/workflows/anchore.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 02ad1658..ba2e71a2 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -2,6 +2,10 @@ name: Anchore Grype vulnerability scan on: [push] +permissions: + contents: read + security-events: write + jobs: build: runs-on: ubuntu-latest From 02e14397d3ce5b21a78b1972e03d94a841f3d656 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:53:20 +0100 Subject: [PATCH 062/135] Feat: Added credit function (npm run license) --- .github/workflows/anchore.yml | 2 +- CREDITS.md | 52 +++++ package-lock.json | 425 ++++++++++++++++++++++++++++++++++ package.json | 4 +- src/misc/credits.sh | 28 +++ 5 files changed, 509 insertions(+), 2 deletions(-) create mode 100644 CREDITS.md create mode 100644 src/misc/credits.sh diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index ba2e71a2..2725a7cc 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -7,7 +7,7 @@ permissions: security-events: write jobs: - build: + anchore: runs-on: ubuntu-latest steps: - name: Set up Grype installation path diff --git a/CREDITS.md b/CREDITS.md new file mode 100644 index 00000000..6ff66a2a --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,52 @@ +### License: (MIT AND CC-BY-3.0) + +| Name | Repository | Publisher | +|------|-------------|-----------| +| spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | + + +### License: Apache-2.0 + +| Name | Repository | Publisher | +|------|-------------|-----------| +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | + + +### License: CC-BY-3.0 + +| Name | Repository | Publisher | +|------|-------------|-----------| +| spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | + + +### License: Python-2.0 + +| Name | Repository | Publisher | +|------|-------------|-----------| +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | + + diff --git a/package-lock.json b/package-lock.json index 847bd243..9776ee4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "dependency-cruiser": "^16.5.0", "eslint": "^9.17.0", "globals": "^15.14.0", + "license-checker": "^25.0.1", "nodemon": "^3.1.7", "ora": "^8.1.1", "prettier": "^3.4.2", @@ -1676,12 +1677,29 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -2264,6 +2282,17 @@ } } }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -2391,6 +2420,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3483,6 +3523,13 @@ "node": ">= 0.4" } }, + "node_modules/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, + "license": "ISC" + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -3888,6 +3935,13 @@ "dev": true, "license": "MIT" }, + "node_modules/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, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3955,6 +4009,153 @@ "node": ">= 0.8.0" } }, + "node_modules/license-checker": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", + "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker": "bin/license-checker" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT" + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/license-checker/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/license-checker/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4745,6 +4946,29 @@ "node": ">=6" } }, + "node_modules/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, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4755,6 +4979,13 @@ "node": ">=0.10.0" } }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true, + "license": "ISC" + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -4951,6 +5182,38 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5341,6 +5604,49 @@ "node": ">=0.10.0" } }, + "node_modules/read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5355,6 +5661,20 @@ "node": ">= 6" } }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "node_modules/readdirp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", @@ -5840,6 +6160,16 @@ "dev": true, "license": "MIT" }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "*" + } + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -5881,6 +6211,73 @@ "node": ">= 10" } }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", + "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, "node_modules/split-ca": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz", @@ -6298,6 +6695,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -6584,6 +6991,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", + "dev": true, + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6600,6 +7014,17 @@ "dev": true, "license": "MIT" }, + "node_modules/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, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/validator": { "version": "13.12.0", "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", diff --git a/package.json b/package.json index fa0e693f..466978d9 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.{yaml,yml} --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", "lint": "npx eslint", - "lint:fix": "npx eslint --fix" + "lint:fix": "npx eslint --fix", + "license": "bash ./src/misc/credits.sh" }, "keywords": [], "author": "Its4Nik", @@ -60,6 +61,7 @@ "dependency-cruiser": "^16.5.0", "eslint": "^9.17.0", "globals": "^15.14.0", + "license-checker": "^25.0.1", "nodemon": "^3.1.7", "ora": "^8.1.1", "prettier": "^3.4.2", diff --git a/src/misc/credits.sh b/src/misc/credits.sh new file mode 100644 index 00000000..8d331509 --- /dev/null +++ b/src/misc/credits.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +if ! command -v jq 2>&1 >/dev/null +then + echo "ERROR: jq could not be found" + exit 1 +fi + + +LICENSE_JSON=$(npx license-checker \ + --exclude 'MIT, MIT-0, MIT OR X11, BSD, ISC, Unlicense, CC0-1.0, Python-2.0: 1' \ + --json) +{ + echo -e "# CREDITS\n" + echo "This file shows all npm packages used in DockStatAPI (also Dev packages)" +} + +jq -r ' + to_entries | + group_by(.value.licenses)[] | + "### License: \(.[0].value.licenses)\n\n" + + "| Name | Repository | Publisher |\n|------|-------------|-----------|\n" + + (map( + "| \(.key) | \(.value.repository // "N/A") | \(.value.publisher // "N/A") |" + ) | join("\n")) + "\n\n" +' <<< "$LICENSE_JSON" >> CREDITS.md + +echo "Markdown file with license information has been created: CREDITS.md" From 71a43ee2ae24991593799810a3b349c74d4b45ea Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:53:56 +0100 Subject: [PATCH 063/135] Feat: Added credit function (npm run license) --- CREDITS.md | 3 +++ src/misc/credits.sh | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CREDITS.md b/CREDITS.md index 6ff66a2a..62f87e6a 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,3 +1,6 @@ +# CREDITS + +This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) | Name | Repository | Publisher | diff --git a/src/misc/credits.sh b/src/misc/credits.sh index 8d331509..8a028a74 100644 --- a/src/misc/credits.sh +++ b/src/misc/credits.sh @@ -10,10 +10,11 @@ fi LICENSE_JSON=$(npx license-checker \ --exclude 'MIT, MIT-0, MIT OR X11, BSD, ISC, Unlicense, CC0-1.0, Python-2.0: 1' \ --json) + { echo -e "# CREDITS\n" echo "This file shows all npm packages used in DockStatAPI (also Dev packages)" -} +} > CREDITS.md jq -r ' to_entries | From 2912bc85f03e9a5f688914a2e7fed14afeebb0d3 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:54:25 +0100 Subject: [PATCH 064/135] Feat: Added credit function (npm run license) --- src/misc/credits.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/misc/credits.sh b/src/misc/credits.sh index 8a028a74..3db14f64 100644 --- a/src/misc/credits.sh +++ b/src/misc/credits.sh @@ -13,7 +13,7 @@ LICENSE_JSON=$(npx license-checker \ { echo -e "# CREDITS\n" - echo "This file shows all npm packages used in DockStatAPI (also Dev packages)" + echo -e "This file shows all npm packages used in DockStatAPI (also Dev packages)\n" } > CREDITS.md jq -r ' From 887d5df8e8215119ab925e43fcd653a1e4fde845 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 19:54:44 +0100 Subject: [PATCH 065/135] Feat: Git pre-commit hook testing` --- CREDITS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CREDITS.md b/CREDITS.md index 62f87e6a..be34b479 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -1,6 +1,7 @@ # CREDITS This file shows all npm packages used in DockStatAPI (also Dev packages) + ### License: (MIT AND CC-BY-3.0) | Name | Repository | Publisher | From b36a03e175d5bb9939a70555a7229ac56883e570 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 21:56:02 +0100 Subject: [PATCH 066/135] Fix: Add linting Chore: Update docs (can't see it here lmao) --- CREDITS.md | 73 ++++++++++++------------- README.md | 15 +++++ package.json | 5 +- src/config/db.ts | 29 ++++------ src/config/initFiles.ts | 1 - src/misc/createEnvDev.sh | 4 ++ src/misc/createEnvFile.sh | 1 + src/routes/frontendController/routes.ts | 3 +- 8 files changed, 70 insertions(+), 61 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index be34b479..050b430b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -4,53 +4,48 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| ----------------- | -------------------------------------------- | -------------------- | | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | - ### License: Apache-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | - +| Name | Repository | Publisher | +| ------------------------------------ | ------------------------------------------------------------- | --------------------- | +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | ### License: CC-BY-3.0 -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| --------------------- | -------------------------------------------------- | -------------------- | | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | - ### License: Python-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | - - +| Name | Repository | Publisher | +| -------------- | ---------------------------------- | --------- | +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | diff --git a/README.md b/README.md index 50246a18..e24b1497 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,21 @@ _⚠️ = Deprecation warning_ - [⚠️ Integrations](https://outline.itsnik.de/s/dockstat/doc/integrations-Agq1oL6HxF) - [⚠️ Backend API reference](https://outline.itsnik.de/s/dockstat/doc/backend-api-reference-YzcBbDvY33) +# Dependencies + +Please see [CREDITS.md](./CREDITS.md). + +To create the credits file use: `npm run license` + +Or if you want it as a pre-commit hook create this file: + +```bash +#!/bin/bash +# .git/hooks/pre-commit + +npm run license +``` + # DockStat(APIs) goals DockStack tries to be a lightweigh and more "dashboard" like then [portainer](https://github.com/portainer/portainer), [cAdvisor](https://github.com/google/cadvisor), [dockge](https://github.com/louislam/dockge), ... diff --git a/package.json b/package.json index 466978d9..8190b94e 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "dev": "npm run local-env-file && nodemon", "dev:trace": "npm run local-env-file && nodemon --trace-uncaught --trace-warnings", "dep": "bash ./src/utils/createDependencyGraph.sh", - "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && bash ./src/utils/createDependencyGraph.sh", + "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && npm run dep", "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", "mini": "bash ./src/misc/minifyDist.sh", @@ -21,7 +21,8 @@ "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.{yaml,yml} --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", "lint": "npx eslint", "lint:fix": "npx eslint --fix", - "license": "bash ./src/misc/credits.sh" + "license": "bash ./src/misc/credits.sh", + "finish": "npm run local-env-file && npm run license && npm run prettier && npm run lint" }, "keywords": [], "author": "Its4Nik", diff --git a/src/config/db.ts b/src/config/db.ts index 80861350..6e2c91c1 100644 --- a/src/config/db.ts +++ b/src/config/db.ts @@ -3,26 +3,21 @@ import logger from "../utils/logger"; const dbPath: string = "./src/data/database.db"; -const db: sqlite3.Database = new sqlite3.Database( - dbPath, - (err: Error | null) => { - if (err) { - logger.error("Error opening database:", err.message); - } else { - db.run( - `CREATE TABLE IF NOT EXISTS data ( +const db: sqlite3.Database = new sqlite3.Database(dbPath, (error: any) => { + if (error) { + logger.error("Error opening database:", error.message); + } else { + db.run( + `CREATE TABLE IF NOT EXISTS data ( id INTEGER PRIMARY KEY AUTOINCREMENT, info TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )`, - () => { - logger.info( - "Database created / opened successfully, table is ready.", - ); - }, - ); - } - }, -); + () => { + logger.info("Database created / opened successfully, table is ready."); + }, + ); + } +}); export default db; diff --git a/src/config/initFiles.ts b/src/config/initFiles.ts index 1f8776a6..79822661 100644 --- a/src/config/initFiles.ts +++ b/src/config/initFiles.ts @@ -1,6 +1,5 @@ import { writeFileSync, existsSync } from "fs"; import logger from "../utils/logger"; -import path from "path"; const files = [ { diff --git a/src/misc/createEnvDev.sh b/src/misc/createEnvDev.sh index dde36f63..4a5a0bbe 100755 --- a/src/misc/createEnvDev.sh +++ b/src/misc/createEnvDev.sh @@ -1,5 +1,9 @@ +#!/bin/bash + +# Version VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" +# Docker if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then RUNNING_IN_DOCKER="true" else diff --git a/src/misc/createEnvFile.sh b/src/misc/createEnvFile.sh index d47eaa9c..754eab5a 100644 --- a/src/misc/createEnvFile.sh +++ b/src/misc/createEnvFile.sh @@ -9,6 +9,7 @@ if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then else RUNNING_IN_DOCKER="false" fi + echo -n "\ { \"VERSION\": \"${VERSION}\", diff --git a/src/routes/frontendController/routes.ts b/src/routes/frontendController/routes.ts index fe5d8411..540444af 100644 --- a/src/routes/frontendController/routes.ts +++ b/src/routes/frontendController/routes.ts @@ -1,5 +1,4 @@ import express from "express"; -import logger from "../../utils/logger"; const router = express.Router(); import { hideContainer, @@ -69,7 +68,7 @@ router.post("/show/:containerName", async (req, res) => { try { await unhideContainer(containerName); res.status(200).json({ message: "Container unhidden successfully." }); - } catch (error: any) { + } catch (error) { res.status(500).json({ error: error.message }); } }); From c2fd998cb8e28d812259fbf61fabf807ec85bbe9 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 22:01:56 +0100 Subject: [PATCH 067/135] Fix: Fixing dep:remove logic --- CREDITS.md | 73 +++---- package-lock.json | 148 --------------- package.json | 2 - src/misc/dependencyGraphs/mermaid-all.txt | 222 ++++++++++++---------- src/utils/removeUnusedDeps.sh | 36 ++-- 5 files changed, 178 insertions(+), 303 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 050b430b..be34b479 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -4,48 +4,53 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) -| Name | Repository | Publisher | -| ----------------- | -------------------------------------------- | -------------------- | +| Name | Repository | Publisher | +|------|-------------|-----------| | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | + ### License: Apache-2.0 -| Name | Repository | Publisher | -| ------------------------------------ | ------------------------------------------------------------- | --------------------- | -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | +| Name | Repository | Publisher | +|------|-------------|-----------| +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | + ### License: CC-BY-3.0 -| Name | Repository | Publisher | -| --------------------- | -------------------------------------------------- | -------------------- | +| Name | Repository | Publisher | +|------|-------------|-----------| | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | + ### License: Python-2.0 -| Name | Repository | Publisher | -| -------------- | ---------------------------------- | --------- | -| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | +| Name | Repository | Publisher | +|------|-------------|-----------| +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | + + diff --git a/package-lock.json b/package-lock.json index 9776ee4d..ba55c01c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "express-rate-limit": "^7.4.1", "https": "^1.0.0", "ipaddr.js": "^2.2.0", - "node-fetch": "^3.3.2", "nodemailer": "^6.9.16", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", @@ -33,7 +32,6 @@ "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", "@types/node": "^22.9.0", - "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", "@types/supports-color": "^8.1.3", "@types/swagger-jsdoc": "^6.0.4", @@ -1152,17 +1150,6 @@ "undici-types": "~6.20.0" } }, - "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "form-data": "^4.0.0" - } - }, "node_modules/@types/nodemailer": { "version": "6.4.17", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", @@ -1715,13 +1702,6 @@ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2136,19 +2116,6 @@ "text-hex": "1.0.x" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", @@ -2256,15 +2223,6 @@ "node": ">= 8" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2324,16 +2282,6 @@ "dev": true, "license": "MIT" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -3070,29 +3018,6 @@ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", "license": "MIT" }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -3202,33 +3127,6 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, - "node_modules/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -4667,43 +4565,6 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "license": "MIT" }, - "node_modules/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==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -7056,15 +6917,6 @@ "node": "^18||>=20" } }, - "node_modules/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==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 8190b94e..9acd9525 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,6 @@ "express-rate-limit": "^7.4.1", "https": "^1.0.0", "ipaddr.js": "^2.2.0", - "node-fetch": "^3.3.2", "nodemailer": "^6.9.16", "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", @@ -52,7 +51,6 @@ "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", "@types/node": "^22.9.0", - "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", "@types/supports-color": "^8.1.3", "@types/swagger-jsdoc": "^6.0.4", diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index 995f295d..d2bae0c3 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -3,123 +3,143 @@ flowchart LR 0["server.ts"] subgraph 1["config"] 2["hostsystem.ts"] -B["db.ts"] -1G["swaggerConfig.ts"] +4["variables.ts"] +C["initFiles.ts"] +F["db.ts"] +1L["swaggerConfig.ts"] end 3["os"] -subgraph 4["controllers"] -5["highAvailability.ts"] -9["proxy.ts"] -A["scheduler.ts"] -C["fetchData.ts"] -R["frontendConfiguration.ts"] +subgraph 5["data"] +6["variables.json"] end -6["util"] -7["init.ts"] -8["process"] -subgraph D["utils"] -E["containerService.ts"] -F["dockerClient.ts"] -U["connectionChecker.ts"] -W["extractHostData.ts"] -X["writeOfflineLog.ts"] -subgraph 12["notifications"] -13["_notify.ts"] -14["discord.ts"] -16["_template.ts"] -17["email.ts"] -18["pushbullet.ts"] -19["pushover.ts"] -1A["slack.ts"] -1B["telegram.ts"] -1C["whatsapp.ts"] +subgraph 7["controllers"] +8["highAvailability.ts"] +D["proxy.ts"] +E["scheduler.ts"] +G["fetchData.ts"] +W["frontendConfiguration.ts"] end -1F["swaggerDocs.ts"] +9["util"] +A["init.ts"] +B["process"] +subgraph H["utils"] +I["containerService.ts"] +J["dockerClient.ts"] +M["rateLimitFS.ts"] +Z["connectionChecker.ts"] +11["extractHostData.ts"] +12["writeOfflineLog.ts"] +subgraph 17["notifications"] +18["_notify.ts"] +19["discord.ts"] +1B["_template.ts"] +1C["email.ts"] +1D["pushbullet.ts"] +1E["pushover.ts"] +1F["slack.ts"] +1G["telegram.ts"] +1H["whatsapp.ts"] end -subgraph G["middleware"] -H["authMiddleware.ts"] -I["checkLock.ts"] -J["rateLimiter.ts"] +1K["swaggerDocs.ts"] end -subgraph K["routes"] -subgraph L["auth"] -M["routes.ts"] +subgraph K["middleware"] +L["authMiddleware.ts"] +N["checkLock.ts"] +O["rateLimiter.ts"] end -subgraph N["data"] -O["routes.ts"] +subgraph P["routes"] +subgraph Q["auth"] +R["routes.ts"] end -subgraph P["frontendController"] -Q["routes.ts"] -end -subgraph S["getter"] +subgraph S["data"] T["routes.ts"] end -subgraph Y["highavailability"] -Z["routes.ts"] +subgraph U["frontendController"] +V["routes.ts"] +end +subgraph X["getter"] +Y["routes.ts"] +end +subgraph 13["highavailability"] +14["routes.ts"] end -subgraph 10["notifications"] -11["routes.ts"] +subgraph 15["notifications"] +16["routes.ts"] end -subgraph 1D["setter"] -1E["routes.ts"] +subgraph 1I["setter"] +1J["routes.ts"] end end -V["net"] -15["https"] +10["net"] +1A["https"] 0-->2 -0-->5 -0-->7 +0-->8 +0-->A +2-->4 2-->3 -5-->6 -7-->9 -7-->A -7-->H -7-->I -7-->J -7-->M -7-->O -7-->Q -7-->T -7-->Z -7-->11 -7-->1E -7-->1F -7-->8 -A-->B +4-->6 +8-->4 +8-->9 A-->C -C-->B -C-->E +A-->D +A-->E +A-->L +A-->N +A-->O +A-->R +A-->T +A-->V +A-->Y +A-->14 +A-->16 +A-->1J +A-->1K +A-->B +D-->4 E-->F -O-->B -Q-->R -T-->A -T-->U -T-->E +E-->G +G-->F +G-->I +I-->J +L-->M +N-->M T-->F -T-->W -T-->X -U-->V -Z-->5 -11-->13 -13-->14 -13-->17 -13-->18 -13-->19 -13-->1A -13-->1B -13-->1C -14-->16 -14-->15 -17-->16 -18-->16 -18-->15 -19-->16 -19-->15 -1A-->16 -1A-->15 -1B-->16 -1B-->15 -1C-->16 -1C-->15 -1E-->A -1F-->1G +V-->W +Y-->E +Y-->Z +Y-->I +Y-->J +Y-->11 +Y-->12 +Z-->10 +14-->8 +16-->18 +18-->19 +18-->1C +18-->1D +18-->1E +18-->1F +18-->1G +18-->1H +19-->4 +19-->1B +19-->1A +1C-->4 +1C-->1B +1D-->4 +1D-->1B +1D-->1A +1E-->4 +1E-->1B +1E-->1A +1F-->4 +1F-->1B +1F-->1A +1G-->4 +1G-->1B +1G-->1A +1H-->4 +1H-->1B +1H-->1A +1J-->E +1K-->1L diff --git a/src/utils/removeUnusedDeps.sh b/src/utils/removeUnusedDeps.sh index df72f4b4..5e806df3 100755 --- a/src/utils/removeUnusedDeps.sh +++ b/src/utils/removeUnusedDeps.sh @@ -2,35 +2,35 @@ echo "Creating unused dependency list" -TMP="$(npx depcheck --ignores @types/node-fetch,uglify-js,@types/supports-color,ipaddr.js,dependency-cruiser,tsx,@types/bcrypt,@types/express,@types/express-handlebars,@types/node,ts-node --quiet --oneline | tail -n 1 | tr -d '\n')" +TMP="$(npx depcheck --ignores https,@typescript-eslint/eslint-plugin,@typescript-eslint/parser,license-checker,uglify-js,@types/supports-color,ipaddr.js,dependency-cruiser,tsx,@types/bcrypt,@types/express,@types/express-handlebars,@types/node,ts-node --quiet --oneline | tail -n 1 | tr -d '\n')" -lines=$(echo "$TMP" | tr -s ' ' '\n' | wc -l) +lines=$(echo -n "$TMP" | tr -s ' ' '\n' | wc -l) if ((lines == 0)); then echo "No unused dependencies." else echo - echo "Removing these unused dependencies:" + echo "Removing these unused dependencies ($lines):" for entry in $TMP; do echo "$entry" done echo -fi -read -n 1 -p "Delete unused dependencies? (y/n) " input -echo + read -n 1 -p "Delete unused dependencies? (y/n) " input + echo -case $input in - Y|y) - COMMAND=$(echo "npm remove $TMP") - $COMMAND - exit 0 - ;; - *) - echo "Aborting" - exit 1 - ;; -esac + case $input in + Y|y) + COMMAND=$(echo "npm remove $TMP") + $COMMAND + exit 0 + ;; + *) + echo "Aborting" + exit 1 + ;; + esac +fi -exit 2 +exit 0 From ba34e961300d705060959062be0b666d4b4ff86d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 22:10:10 +0100 Subject: [PATCH 068/135] Fix: Fixing some logic things and docs --- src/misc/dependencyGraphs/mermaid-all.txt | 226 ++++++++++------------ src/utils/createDependencyGraph.sh | 5 +- 2 files changed, 107 insertions(+), 124 deletions(-) diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index d2bae0c3..e81fdf84 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -1,145 +1,127 @@ flowchart LR 0["server.ts"] -subgraph 1["config"] -2["hostsystem.ts"] -4["variables.ts"] -C["initFiles.ts"] -F["db.ts"] -1L["swaggerConfig.ts"] +subgraph 1["controllers"] +2["highAvailability.ts"] +A["proxy.ts"] +B["scheduler.ts"] +D["fetchData.ts"] +T["frontendConfiguration.ts"] end -3["os"] -subgraph 5["data"] -6["variables.json"] +3["util"] +subgraph 4["config"] +5["variables.ts"] +9["initFiles.ts"] +C["db.ts"] +1F["swaggerConfig.ts"] end -subgraph 7["controllers"] -8["highAvailability.ts"] -D["proxy.ts"] -E["scheduler.ts"] -G["fetchData.ts"] -W["frontendConfiguration.ts"] +subgraph 6["data"] +7["variables.json"] end -9["util"] -A["init.ts"] -B["process"] -subgraph H["utils"] -I["containerService.ts"] -J["dockerClient.ts"] -M["rateLimitFS.ts"] -Z["connectionChecker.ts"] -11["extractHostData.ts"] -12["writeOfflineLog.ts"] -subgraph 17["notifications"] -18["_notify.ts"] -19["discord.ts"] -1B["_template.ts"] -1C["email.ts"] -1D["pushbullet.ts"] -1E["pushover.ts"] -1F["slack.ts"] -1G["telegram.ts"] -1H["whatsapp.ts"] +8["init.ts"] +subgraph E["utils"] +F["containerService.ts"] +G["dockerClient.ts"] +J["rateLimitFS.ts"] +W["connectionChecker.ts"] +X["writeOfflineLog.ts"] +subgraph 12["notifications"] +13["_notify.ts"] +14["discord.ts"] +15["_template.ts"] +16["email.ts"] +17["pushbullet.ts"] +18["pushover.ts"] +19["slack.ts"] +1A["telegram.ts"] +1B["whatsapp.ts"] end -1K["swaggerDocs.ts"] +1E["swaggerDocs.ts"] end -subgraph K["middleware"] -L["authMiddleware.ts"] -N["checkLock.ts"] -O["rateLimiter.ts"] +subgraph H["middleware"] +I["authMiddleware.ts"] +K["checkLock.ts"] +L["rateLimiter.ts"] end -subgraph P["routes"] -subgraph Q["auth"] -R["routes.ts"] +subgraph M["routes"] +subgraph N["auth"] +O["routes.ts"] end -subgraph S["data"] -T["routes.ts"] +subgraph P["data"] +Q["routes.ts"] end -subgraph U["frontendController"] -V["routes.ts"] +subgraph R["frontendController"] +S["routes.ts"] end -subgraph X["getter"] -Y["routes.ts"] +subgraph U["getter"] +V["routes.ts"] end -subgraph 13["highavailability"] -14["routes.ts"] +subgraph Y["highavailability"] +Z["routes.ts"] end -subgraph 15["notifications"] -16["routes.ts"] +subgraph 10["notifications"] +11["routes.ts"] end -subgraph 1I["setter"] -1J["routes.ts"] +subgraph 1C["setter"] +1D["routes.ts"] end end -10["net"] -1A["https"] 0-->2 0-->8 -0-->A -2-->4 +2-->5 2-->3 -4-->6 -8-->4 +5-->7 8-->9 -A-->C -A-->D -A-->E -A-->L -A-->N -A-->O -A-->R -A-->T -A-->V -A-->Y -A-->14 -A-->16 -A-->1J -A-->1K -A-->B -D-->4 -E-->F -E-->G -G-->F -G-->I +8-->A +8-->B +8-->I +8-->K +8-->L +8-->O +8-->Q +8-->S +8-->V +8-->Z +8-->11 +8-->1D +8-->1E +A-->5 +B-->C +B-->D +D-->C +D-->F +F-->G I-->J -L-->M -N-->M -T-->F +K-->J +Q-->C +S-->T +V-->B V-->W -Y-->E -Y-->Z -Y-->I -Y-->J -Y-->11 -Y-->12 -Z-->10 -14-->8 -16-->18 -18-->19 -18-->1C -18-->1D -18-->1E -18-->1F -18-->1G -18-->1H -19-->4 -19-->1B -19-->1A -1C-->4 -1C-->1B -1D-->4 -1D-->1B -1D-->1A -1E-->4 -1E-->1B -1E-->1A -1F-->4 -1F-->1B -1F-->1A -1G-->4 -1G-->1B -1G-->1A -1H-->4 -1H-->1B -1H-->1A -1J-->E -1K-->1L +V-->F +V-->G +V-->X +Z-->2 +11-->13 +13-->14 +13-->16 +13-->17 +13-->18 +13-->19 +13-->1A +13-->1B +14-->5 +14-->15 +16-->5 +16-->15 +17-->5 +17-->15 +18-->5 +18-->15 +19-->5 +19-->15 +1A-->5 +1A-->15 +1B-->5 +1B-->15 +1D-->B +1E-->1F diff --git a/src/utils/createDependencyGraph.sh b/src/utils/createDependencyGraph.sh index c8229992..9c220f7a 100755 --- a/src/utils/createDependencyGraph.sh +++ b/src/utils/createDependencyGraph.sh @@ -1,6 +1,7 @@ #!/bin/bash cd src || exit 1 TMP=$(mktemp) +IGNORE="../node_modules|logger|.dependency-cruiser|path|fs|os|https|net|process" cat ./server.ts | grep "./routes" | awk '{print $2,$4}' > $TMP @@ -14,7 +15,7 @@ spawn_worker(){ npx depcruise \ -p cli-feedback \ -T mermaid \ - -x "../node_modules|logger|.dependency-cruiser|path|fs|net" \ + -x "$IGNORE" \ -f ./misc/dependencyGraphs/mermaid-${route}.txt \ ${target_route} || exit 1 } @@ -26,7 +27,7 @@ done < <(cat $TMP) npx depcruise \ -p cli-feedback \ -T mermaid \ - -x "../node_modules|logger|.dependency-cruiser|path|fs" \ + -x "$IGNORE" \ -f ./misc/dependencyGraphs/mermaid-all.txt \ ./server.ts || exit 1 From 9fdb89f6a3769d28f33f6b666f45f6f1aad91732 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 22:18:41 +0100 Subject: [PATCH 069/135] Fix: Fixing build (hopefully) --- src/routes/highavailability/routes.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/routes/highavailability/routes.ts b/src/routes/highavailability/routes.ts index bc4cb794..3fadb02e 100644 --- a/src/routes/highavailability/routes.ts +++ b/src/routes/highavailability/routes.ts @@ -3,9 +3,7 @@ import { Router, Request, Response } from "express"; import logger from "../../utils/logger"; import { readConfig, - synchronizeFilesWithNodes, prepareFilesForSync, - HighAvailabilityConfig, ensureFileExists, } from "../../controllers/highAvailability"; @@ -44,7 +42,7 @@ router.get("/config", async (req: Request, res: Response) => { router.post( "/sync", async ( - req: Request<{}, {}, SyncRequestBody>, + req: Request<{}, {}, SyncRequestBody>, // eslint-disable-line res: Response, ): Promise => { try { From 8e0967be7d789ed48c07f058971b6850db2044a8 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 27 Dec 2024 22:24:17 +0100 Subject: [PATCH 070/135] Fix: Fixing build (hopefully) --- src/routes/frontendController/routes.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/frontendController/routes.ts b/src/routes/frontendController/routes.ts index 540444af..0fce63e0 100644 --- a/src/routes/frontendController/routes.ts +++ b/src/routes/frontendController/routes.ts @@ -68,7 +68,7 @@ router.post("/show/:containerName", async (req, res) => { try { await unhideContainer(containerName); res.status(200).json({ message: "Container unhidden successfully." }); - } catch (error) { + } catch (error: any) { res.status(500).json({ error: error.message }); } }); From ea8693acb9f1b45da1dce5d8eeadce79212b0fd7 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 10:26:06 +0100 Subject: [PATCH 071/135] Fix: Linting (No more any :D) --- src/config/db.ts | 6 +- src/config/loggerConfig.ts | 1 - src/config/swaggerConfig.ts | 2 +- src/controllers/containerController.ts | 14 +- src/controllers/fetchData.ts | 4 +- src/controllers/frontendConfiguration.ts | 111 +- src/controllers/highAvailability.ts | 2 +- src/controllers/notificationController.ts | 2 + src/controllers/scheduler.ts | 6 +- src/data/frontendConfiguration.json | 12 +- src/init.ts | 2 +- src/middleware/authMiddleware.ts | 2 +- src/routes/auth/routes.ts | 33 +- src/routes/data/routes.ts | 46 +- src/routes/frontendController/routes.ts | 40 +- src/routes/getter/routes.ts | 41 +- src/routes/notifications/routes.ts | 6 +- src/routes/setter/routes.ts | 10 +- src/typings/dockerConfig.ts | 10 + src/typings/frontendConfig.ts | 12 + src/typings/states.ts | 10 + src/typings/table.ts | 7 + src/utils/connectionChecker.ts | 4 +- src/utils/containerService.ts | 18 +- src/utils/dockerClient.ts | 2 +- src/utils/notifications/_notify.ts | 5 +- src/utils/notifications/_template.ts | 29 +- src/utils/notifications/email.ts | 4 +- src/utils/swaggerDocs.ts | 4 +- src/utils/writeOfflineLog.ts | 26 - yarn.lock | 2852 --------------------- 31 files changed, 259 insertions(+), 3064 deletions(-) create mode 100644 src/typings/dockerConfig.ts create mode 100644 src/typings/frontendConfig.ts create mode 100644 src/typings/states.ts create mode 100644 src/typings/table.ts delete mode 100644 src/utils/writeOfflineLog.ts delete mode 100644 yarn.lock diff --git a/src/config/db.ts b/src/config/db.ts index 6e2c91c1..edfe3832 100644 --- a/src/config/db.ts +++ b/src/config/db.ts @@ -3,9 +3,9 @@ import logger from "../utils/logger"; const dbPath: string = "./src/data/database.db"; -const db: sqlite3.Database = new sqlite3.Database(dbPath, (error: any) => { - if (error) { - logger.error("Error opening database:", error.message); +const db: sqlite3.Database = new sqlite3.Database(dbPath, (error: unknown) => { + if (error as Error) { + logger.error("Error opening database:", (error as Error).message); } else { db.run( `CREATE TABLE IF NOT EXISTS data ( diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts index 45feb5c7..5d1a33e4 100644 --- a/src/config/loggerConfig.ts +++ b/src/config/loggerConfig.ts @@ -7,7 +7,6 @@ const red = "\x1b[31m"; const green = "\x1b[32m"; const yellow = "\x1b[33m"; const blue = "\x1b[34m"; -const pink = "\x1b[38;5;213m"; // Pink color for sync logs const ignoreExitListenerLogs = format((info) => { if ( diff --git a/src/config/swaggerConfig.ts b/src/config/swaggerConfig.ts index 630805e9..cab967f8 100644 --- a/src/config/swaggerConfig.ts +++ b/src/config/swaggerConfig.ts @@ -18,7 +18,7 @@ const options: { }; }; security: Array<{ - passwordAuth: any[]; + passwordAuth: unknown[]; }>; }; apis: string[]; diff --git a/src/controllers/containerController.ts b/src/controllers/containerController.ts index 1532681e..8d3bef30 100644 --- a/src/controllers/containerController.ts +++ b/src/controllers/containerController.ts @@ -10,12 +10,12 @@ const getContainers = async (req: Request, res: Response): Promise => { const containers = await docker.listContainers(); res.status(200).json(containers); - } catch (error: any) { + } catch (error: unknown) { logger.error( - `Error fetching containers from host: ${host} - ${error.message || "Unknown error"} - Full error: ${JSON.stringify(error, null, 2)}`, + `Error fetching containers from host: ${host} - ${(error as Error).message || "Unknown error"} - Full error: ${JSON.stringify(error, null, 2)}`, ); res.status(500).json({ - error: `Error fetching containers: ${error.message || "Unknown error"}`, + error: `Error fetching containers: ${(error as Error).message || "Unknown error"}`, }); } }; @@ -36,13 +36,15 @@ const getContainerStats = async ( `Successfully fetched stats for container: ${containerID} from host: ${containerHost}`, ); res.status(200).json(stats); - } catch (error: any) { + } catch (error: unknown) { logger.error( - `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${error.message}`, + `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${(error as Error).message}`, ); res .status(500) - .json({ error: `Error fetching container stats: ${error.message}` }); + .json({ + error: `Error fetching container stats: ${(error as Error).message}`, + }); } }; diff --git a/src/controllers/fetchData.ts b/src/controllers/fetchData.ts index 238e8262..dfc24878 100644 --- a/src/controllers/fetchData.ts +++ b/src/controllers/fetchData.ts @@ -66,9 +66,9 @@ const fetchData = async (): Promise => { } else { logger.info("No state change detected, notifications not triggered."); } - } catch (error: any) { + } catch (error: unknown) { logger.error( - `Error fetching data: ${JSON.stringify(error)} \nStack trace: ${error.stack}`, + `Error fetching data: ${JSON.stringify(error)} \nStack trace: ${(error as Error).stack}`, ); } }; diff --git a/src/controllers/frontendConfiguration.ts b/src/controllers/frontendConfiguration.ts index 4d31943e..e8e035c1 100644 --- a/src/controllers/frontendConfiguration.ts +++ b/src/controllers/frontendConfiguration.ts @@ -4,6 +4,7 @@ const dataPath: string = "./src/data/frontendConfiguration.json"; const expression: string = "https?://(www.)?[-a-zA-Z0-9@:%._+~#=]{1,256}.[a-zA-Z0-9()]{1,6}([-a-zA-Z0-9()@:%_+.~#?&//=]*)"; const regex = new RegExp(expression); +import { FrontendConfig } from "../typings/frontendConfig"; /////////////////////////////////////////////////////////////// // Hide Containers: @@ -11,7 +12,7 @@ async function hideContainer(containerName: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -21,9 +22,9 @@ async function hideContainer(containerName: string) { data.push({ name: containerName, hidden: true }); await saveData(data); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -31,7 +32,7 @@ async function unhideContainer(containerName: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -39,9 +40,9 @@ async function unhideContainer(containerName: string) { await saveData(data); cleanupData(); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -51,7 +52,7 @@ async function addTagToContainer(containerName: string, tag: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -64,9 +65,9 @@ async function addTagToContainer(containerName: string, tag: string) { data.push({ name: containerName, tags: [tag] }); await saveData(data); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -74,19 +75,19 @@ async function removeTagFromContainer(containerName: string, tag: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1 && data[containerIndex].tags) { data[containerIndex].tags = data[containerIndex].tags.filter( - (t: any) => t !== tag, + (t) => t !== tag, ); await saveData(data); cleanupData(); } - } catch (error: any) { + } catch (error: unknown) { logger.error(error); - throw new Error(error); + throw new Error(error as string); } } @@ -94,9 +95,9 @@ async function removeTagFromContainer(containerName: string, tag: string) { // Pin containers async function pinContainer(containerName: string) { try { - const data: any = await readData(); - const containerIndex: number = data.findIndex( - (container: any) => container.name === containerName, + const data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -106,9 +107,9 @@ async function pinContainer(containerName: string) { data.push({ name: containerName, pinned: true }); await saveData(data); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -116,7 +117,7 @@ async function unpinContainer(containerName: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -124,9 +125,9 @@ async function unpinContainer(containerName: string) { await saveData(data); cleanupData(); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -135,9 +136,9 @@ async function unpinContainer(containerName: string) { async function setLink(containerName: string, link: string) { if (link.match(regex)) { try { - const data: any = await readData(); - const containerIndex: any = data.findIndex( - (container: any) => container.name === containerName, + const data = await readData(); + const containerIndex = data.findIndex( + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -147,9 +148,9 @@ async function setLink(containerName: string, link: string) { data.push({ name: containerName, link: `${link}` }); await saveData(data); } - } catch (error: any) { + } catch (error: unknown) { logger.error(error); - throw new Error(error); + throw new Error(error as string); } } else { logger.error(`Provided link is not valid: ${link}`); @@ -161,7 +162,7 @@ async function removeLink(containerName: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -169,9 +170,9 @@ async function removeLink(containerName: string) { await saveData(data); cleanupData(); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -181,7 +182,7 @@ async function setIcon(containerName: string, icon: string, custom: boolean) { try { const data = await readData(); const containerIndex: number = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (custom === true) { @@ -199,9 +200,9 @@ async function setIcon(containerName: string, icon: string, custom: boolean) { data.push({ name: containerName, icon: `${icon}` }); await saveData(data); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -209,7 +210,7 @@ async function removeIcon(containerName: string) { try { const data = await readData(); const containerIndex = data.findIndex( - (container: any) => container.name === containerName, + (container) => container.name === containerName, ); if (containerIndex !== -1) { @@ -217,9 +218,9 @@ async function removeIcon(containerName: string) { await saveData(data); cleanupData(); } - } catch (error: any) { - logger.error(error); - throw new Error(error); + } catch (error: unknown) { + logger.error(error as Error); + throw new Error(error as string); } } @@ -227,11 +228,13 @@ async function removeIcon(containerName: string) { // Data specific functionss async function readData() { try { - const data = await fs.promises.readFile(dataPath, "utf-8"); - return JSON.parse(data); - } catch (error: any) { - console.error("readData"); - if (error.code === "ENOENT") { + const data: FrontendConfig = JSON.parse( + await fs.promises.readFile(dataPath, "utf-8"), + ); + return data; + } catch (error: unknown) { + console.error(`Error while reading ${dataPath}: ${error as Error}`); + if (error as Error) { await saveData([]); return []; } else { @@ -240,7 +243,7 @@ async function readData() { } } -async function saveData(data: any) { +async function saveData(data: FrontendConfig) { try { await fs.promises.writeFile( dataPath, @@ -248,15 +251,15 @@ async function saveData(data: any) { "utf-8", ); logger.info("Succesfully wrote to file"); - } catch (error: any) { - logger.error(error); + } catch (error: unknown) { + logger.error(error as Error); } } async function cleanupData() { try { const data = await readData(); - let cleanedData = []; + let cleanedData: FrontendConfig = []; if (data && Array.isArray(data)) { cleanedData = data.filter((container) => { @@ -273,8 +276,8 @@ async function cleanupData() { } await saveData(cleanedData); - } catch (error: any) { - logger.error(error); + } catch (error: unknown) { + logger.error(error as Error); } } diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index dd16bf6c..7bf7dc77 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -102,7 +102,7 @@ async function readConfig(): Promise { fs.readFileSync(haMasterPath, "utf-8"), ); return data; - } catch (error: any) { + } catch (error: unknown) { logger.error(`Error reading HA-Config: ${(error as Error).message}`); return null; } finally { diff --git a/src/controllers/notificationController.ts b/src/controllers/notificationController.ts index ad0b1bcc..0ece9553 100644 --- a/src/controllers/notificationController.ts +++ b/src/controllers/notificationController.ts @@ -56,3 +56,5 @@ async function sendNotification(containerId: string) { notify("whatsapp", containerId); } } + +export default sendNotification; diff --git a/src/controllers/scheduler.ts b/src/controllers/scheduler.ts index 763b67f9..caa19481 100644 --- a/src/controllers/scheduler.ts +++ b/src/controllers/scheduler.ts @@ -11,7 +11,7 @@ const scheduleFetch = () => { try { fetchData(); cleanupOldEntries(); - } catch (error: any) { + } catch (error: unknown) { logger.error(`Error during scheduled fetch: ${error}`); } @@ -81,8 +81,8 @@ const cleanupOldEntries = async () => { try { db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo, Error); logger.info("Old entries cleared from the database."); - } catch (Error: any) { - logger.error(`Error clearing old entries: ${Error.message}`); + } catch (Error: unknown) { + logger.error(`Error clearing old entries: ${(Error as Error).message}`); } }; diff --git a/src/data/frontendConfiguration.json b/src/data/frontendConfiguration.json index 4697f960..884e0e20 100644 --- a/src/data/frontendConfiguration.json +++ b/src/data/frontendConfiguration.json @@ -2,7 +2,15 @@ { "name": "test", "tags": [ - "123" - ] + "123", + "123", + "321" + ], + "link": "https://google.com", + "icon": "custom/test.png" + }, + { + "name": "test2", + "pinned": true } ] \ No newline at end of file diff --git a/src/init.ts b/src/init.ts index 119950c0..8c757379 100644 --- a/src/init.ts +++ b/src/init.ts @@ -27,7 +27,7 @@ const initializeApp = (app: express.Application): void => { next(), ); - swaggerDocs(app as any); + swaggerDocs(app); trustedProxies(app); scheduleFetch(); diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 08ffd219..500a7fa8 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -43,7 +43,7 @@ async function authMiddleware( logger.debug("Authentication succesfull"); next(); - } catch (error: any) { + } catch (error: unknown) { logger.error("Error in authMiddleware:", error); res.status(500).json({ message: "Internal server error" }); } diff --git a/src/routes/auth/routes.ts b/src/routes/auth/routes.ts index 4af13884..f7e0b18e 100644 --- a/src/routes/auth/routes.ts +++ b/src/routes/auth/routes.ts @@ -7,11 +7,6 @@ const passwordBool: string = "./src/data/usePassword.txt"; const saltRounds: number = 10; const router: Router = Router(); -let passwordData: { - hash: string; - salt: string; -}; - async function authEnabled(): Promise { let isAuthEnabled: boolean = false; let data: string = ""; @@ -19,8 +14,8 @@ async function authEnabled(): Promise { data = await fs.readFile(passwordBool, "utf8"); isAuthEnabled = data.trim() === "true"; return isAuthEnabled; - } catch (error: any) { - logger.error("Error reading file: ", error); + } catch (error: unknown) { + logger.error("Error reading file: ", error as Error); return isAuthEnabled; } } @@ -30,8 +25,8 @@ async function readPasswordFile() { try { data = await fs.readFile(passwordFile, "utf8"); return data; - } catch (error: any) { - logger.error("Could not read saved password: ", error); + } catch (error: unknown) { + logger.error("Could not read saved password: ", error as Error); return data; } } @@ -42,8 +37,8 @@ async function writePasswordFile(passwordData: string) { setTrue(); logger.debug("Authentication enabled"); return "Authentication enabled"; - } catch (error: any) { - logger.error("Error writing password file:", error); + } catch (error: unknown) { + logger.error("Error writing password file:", error as Error); return error; } } @@ -53,8 +48,8 @@ async function setTrue() { await fs.writeFile(passwordBool, "true", "utf8"); logger.info(`Enabled authentication`); return; - } catch (error: any) { - logger.error("Error writing to the file:", error); + } catch (error: unknown) { + logger.error("Error writing to the file:", error as Error); return; } } @@ -64,8 +59,8 @@ async function setFalse() { await fs.writeFile(passwordBool, "false", "utf8"); logger.info(`Disabled authentication`); return; - } catch (error: any) { - logger.error("Error writing to the file:", error); + } catch (error: unknown) { + logger.error("Error writing to the file:", error as Error); return; } } @@ -118,8 +113,8 @@ router.post("/enable", async (req: Request, res: Response): Promise => { res .status(200) .json({ message: "Password Authentication enabled successfully" }); - } catch (error) { - logger.error(`Error enabling password authentication: ${error}`); + } catch (error: unknown) { + logger.error(`Error enabling password authentication: ${error as Error}`); res.status(500).json({ message: "An error occurred" }); } }); @@ -165,8 +160,8 @@ router.post("/disable", async (req: Request, res: Response): Promise => { await setFalse(); // Assuming this is an async function res.status(200).json({ message: "Authentication disabled" }); - } catch (error) { - logger.error(`Error disabling authentication: ${error}`); + } catch (error: unknown) { + logger.error(`Error disabling authentication: ${error as Error}`); res.status(500).json({ message: "An error occurred" }); } }); diff --git a/src/routes/data/routes.ts b/src/routes/data/routes.ts index 0e9a6e36..108fafe4 100644 --- a/src/routes/data/routes.ts +++ b/src/routes/data/routes.ts @@ -2,14 +2,19 @@ import express from "express"; const router = express.Router(); import db from "../../config/db"; import logger from "../../utils/logger"; +import Table from "../../typings/table"; interface DataRow { info: string; } -function formatRows(rows: DataRow[]): Record { +function formatRows(rows: DataRow[]): Record { return rows.reduce( - (acc: Record, row, index: number): Record => { + ( + acc: Record, + row, + index: number, + ): Record => { acc[index] = JSON.parse(row.info); return acc; }, @@ -88,26 +93,31 @@ function formatRows(rows: DataRow[]): Record { router.get("/latest", (req, res) => { db.get( "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", - (error, row: any) => { + (error: unknown, row: Partial> | undefined) => { if (error) { - logger.error("Error fetching latest data:", error.message); + logger.error("Error fetching latest data:", (error as Error).message); return res.status(500).json({ error: "Internal server error" }); } - if (!row) { + if (!row || !row.info) { logger.warn("No data available for /data/latest"); return res.status(404).json({ error: "No data available" }); } logger.debug("Fetching /data/latest"); - res.json(JSON.parse(row.info)); + try { + res.json(JSON.parse(row.info)); + } catch (error: unknown) { + logger.error("Error parsing data:", (error as Error).message); + res.status(500).json({ error: "Data format error" }); + } }, ); }); /** * @swagger - * /data/time/24h: + * /data/all: * get: * summary: Retrieve container statistics entries from the last 24 hours * tags: [Database queries] @@ -152,17 +162,27 @@ router.get("/latest", (req, res) => { * type: number * example: 3072 */ -router.get("/time/24h", (req, res) => { +router.get("/all", (req, res) => { const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + db.all( "SELECT info FROM data WHERE timestamp >= ?", [oneDayAgo], - (error, rows: DataRow[]) => { + (error: unknown, rows: Pick[] | undefined) => { if (error) { - logger.error("Error fetching data from last 24 hours:", error.message); + logger.error( + "Error fetching data from last 24 hours:", + (error as Error).message, + ); return res.status(500).json({ error: "Internal server error" }); } + logger.debug("Fetching /data/time/24h"); + if (!rows || rows.length === 0) { + logger.warn("No data available for /data/time/24h"); + return res.status(404).json({ error: "No data available" }); + } + res.json(formatRows(rows)); }, ); @@ -188,9 +208,9 @@ router.get("/time/24h", (req, res) => { * example: "Database cleared successfully." */ router.delete("/clear", (req, res) => { - db.run("DELETE FROM data", (err) => { - if (err) { - logger.error("Error clearing the database:", err.message); + db.run("DELETE FROM data", (error: unknown) => { + if (error) { + logger.error("Error clearing the database:", (error as Error).message); return res.status(500).json({ error: "Internal server error" }); } logger.debug("Database cleared successfully"); diff --git a/src/routes/frontendController/routes.ts b/src/routes/frontendController/routes.ts index 0fce63e0..0de95fe9 100644 --- a/src/routes/frontendController/routes.ts +++ b/src/routes/frontendController/routes.ts @@ -68,8 +68,8 @@ router.post("/show/:containerName", async (req, res) => { try { await unhideContainer(containerName); res.status(200).json({ message: "Container unhidden successfully." }); - } catch (error: any) { - res.status(500).json({ error: error.message }); + } catch (error: unknown) { + res.status(500).json({ error: (error as Error).message }); } }); @@ -126,8 +126,8 @@ router.post("/tag/:containerName/:tag", async (req, res) => { try { await addTagToContainer(containerName, tag); res.json({ success: true, message: "Tag added successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -178,8 +178,8 @@ router.post("/pin/:containerName", async (req, res) => { try { await pinContainer(containerName); res.json({ success: true, message: "Container pinned successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -236,8 +236,8 @@ router.post("/add-link/:containerName/:link", async (req, res) => { try { await setLink(containerName, link); res.json({ success: true, message: "Link added successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -304,8 +304,8 @@ router.post( await setIcon(containerName, icon, custom); res.json({ success: true, message: "Icon added successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }, ); @@ -366,8 +366,8 @@ router.delete("/hide/:containerName", async (req, res) => { try { await hideContainer(target); res.json({ success: true, message: `Container, ${target}, hidden.` }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -424,8 +424,8 @@ router.delete("/remove-tag/:containerName/:tag", async (req, res) => { try { await removeTagFromContainer(containerName, tag); res.json({ success: true, message: "Tag removed successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -476,8 +476,8 @@ router.delete("/unpin/:containerName", async (req, res) => { try { await unpinContainer(containerName); res.json({ success: true, message: "Container unpinned successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -528,8 +528,8 @@ router.delete("/remove-link/:containerName", async (req, res) => { try { await removeLink(containerName); res.json({ success: true, message: "Link removed successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); @@ -580,8 +580,8 @@ router.delete("/remove-icon/:containerName", async (req, res) => { try { await removeIcon(containerName); res.json({ success: true, message: "Icon removed successfully." }); - } catch (error: any) { - res.status(500).json({ success: false, error: error.message }); + } catch (error: unknown) { + res.status(500).json({ success: false, error: (error as Error).message }); } }); diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index 8e3c6955..d278075c 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -1,6 +1,5 @@ import extractRelevantData from "../../utils/extractHostData"; import { Router, Request, Response } from "express"; -import { writeOfflineLog, readOfflineLog } from "../../utils/writeOfflineLog"; import getDockerClient from "../../utils/dockerClient"; import fetchAllContainers from "../../utils/containerService"; import { getCurrentSchedule } from "../../controllers/scheduler"; @@ -10,6 +9,7 @@ import checkReachability from "../../utils/connectionChecker"; const configPath = "./src/data/dockerConfig.json"; const router = Router(); const userConf = "./src/data/user.conf"; +import { dockerConfig } from "../../typings/dockerConfig"; /** * @swagger @@ -35,17 +35,17 @@ router.get("/hosts", (req: Request, res: Response) => { logger.info(`Fetching config: ${configPath}`); try { const rawData = fs.readFileSync(configPath, "utf-8"); - const config = JSON.parse(rawData); + const config: dockerConfig = JSON.parse(rawData); if (!config.hosts) { throw new Error("No hosts defined in configuration."); } - const hosts = config.hosts.map((host: any) => host.name); + const hosts = config.hosts.map((host) => host.name); logger.debug("Fetching all available Docker hosts"); res.status(200).json({ hosts }); - } catch (error: any) { - logger.error("Error fetching hosts: " + error.message); + } catch (error: unknown) { + logger.error("Error fetching hosts: " + (error as Error).message); res.status(500).json({ error: "Failed to fetch Docker hosts" }); } }); @@ -86,8 +86,8 @@ router.get("/system", (req: Request, res: Response) => { res.status(500).json({ error: `Error received empty ${userConf}` }); } res.status(200).json(config); - } catch (error: any) { - logger.error(`Could not fetch ${userConf}: ${error}`); + } catch (error: unknown) { + logger.error(`Could not fetch ${userConf}: ${error as Error}`); res.status(500).json({ error: `Failed to fetch ${userConf}` }); } }); @@ -142,14 +142,13 @@ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { const version = await docker.version(); const relevantData = extractRelevantData({ hostName, info, version }); - writeOfflineLog(JSON.stringify(relevantData)); res.status(200).json(relevantData); - } catch (error: any) { + } catch (error: unknown) { logger.error( - `Error fetching stats for host: ${hostName} - ${error.message || "Unknown error"}`, + `Error fetching stats for host: ${hostName} - ${(error as Error).message || "Unknown error"}`, ); res.status(500).json({ - error: `Error fetching host stats: ${error.message || "Unknown error"}`, + error: `Error fetching host stats: ${(error as Error).message || "Unknown error"}`, }); } }); @@ -233,8 +232,8 @@ router.get("/containers", async (req: Request, res: Response) => { const allContainerData = await fetchAllContainers(); logger.debug("Fetched /api/containers"); res.status(200).json(allContainerData); - } catch (error: any) { - logger.error(`Error fetching containers: ${error.message}`); + } catch (error: unknown) { + logger.error(`Error fetching containers: ${(error as Error).message}`); res.status(500).json({ error: "Failed to fetch containers" }); } }); @@ -270,8 +269,10 @@ router.get("/config", async (req: Request, res: Response) => { const jsonData = JSON.parse(rawData.toString()); logger.debug("Fetching /api/config"); res.status(200).json(jsonData); - } catch (error: any) { - logger.error("Error loading dockerConfig.json: " + error.message); + } catch (error: unknown) { + logger.error( + "Error loading dockerConfig.json: " + (error as Error).message, + ); res.status(500).json({ error: "Failed to load Docker configuration" }); } }); @@ -334,8 +335,8 @@ router.get("/status", async (req: Request, res: Response) => { try { const jsonData = await checkReachability(); res.status(200).json(jsonData); - } catch (error: any) { - logger.error(`Error while fetching data: ${error}`); + } catch (error: unknown) { + logger.error(`Error while fetching data: ${error as Error}`); } }); @@ -398,8 +399,10 @@ router.get("/frontend-config", (req: Request, res: Response) => { const jsonData = JSON.parse(rawData.toString()); res.status(200).json(jsonData); - } catch (error: any) { - logger.error("Error loading frontendConfiguration.json: " + error.message); + } catch (error: unknown) { + logger.error( + "Error loading frontendConfiguration.json: " + (error as Error).message, + ); res.status(500).json({ error: "Failed to load Frontend configuration" }); } }); diff --git a/src/routes/notifications/routes.ts b/src/routes/notifications/routes.ts index 262d48f3..17cf6986 100644 --- a/src/routes/notifications/routes.ts +++ b/src/routes/notifications/routes.ts @@ -12,7 +12,7 @@ interface TemplateData { text: string; } -function isTemplateData(data: any): data is TemplateData { +function isTemplateData(data: TemplateData): data is TemplateData { return ( data !== null && typeof data === "object" && typeof data.text === "string" ); @@ -169,8 +169,8 @@ router.post("/test/:type/:containerId", async (req: Request, res: Response) => { try { await notify(type, containerId); res.json({ success: true, message: `Sent test notification to ${type}` }); - } catch (error: any) { - res.json({ success: false, message: `Errored: ${error}` }); + } catch (error: unknown) { + res.json({ success: false, message: `Errored: ${error as Error}` }); } }); diff --git a/src/routes/setter/routes.ts b/src/routes/setter/routes.ts index fcffeef9..96915a92 100644 --- a/src/routes/setter/routes.ts +++ b/src/routes/setter/routes.ts @@ -1,9 +1,9 @@ import { setFetchInterval, parseInterval } from "../../controllers/scheduler"; import logger from "../../utils/logger"; -import { Router, Request, Response } from "express"; +import express, { Router, Request, Response } from "express"; import fs from "fs"; -const router = Router(); +const router: Router = express.Router(); const configPath: string = "./src/data/dockerConfig.json"; interface Host { @@ -101,20 +101,20 @@ router.put( * 400: * description: Invalid interval format or out of range. */ -router.put("/scheduler", (req: any, res: any) => { +router.put("/scheduler", (req: Request, res: Response) => { const interval = req.query.interval as string; try { const newInterval = parseInterval(interval); if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { - return res + res .status(400) .json({ error: "Interval must be between 5 minutes and 6 hours." }); } setFetchInterval(newInterval); - res.json({ message: `Fetch interval set to ${interval}.` }); + res.status(200).json({ message: `Fetch interval set to ${interval}.` }); } catch (error: unknown) { const err = error as Error; logger.error("Error setting fetch interval: " + err.message); diff --git a/src/typings/dockerConfig.ts b/src/typings/dockerConfig.ts new file mode 100644 index 00000000..fea0f4ec --- /dev/null +++ b/src/typings/dockerConfig.ts @@ -0,0 +1,10 @@ +interface target { + name: string; + url: string; + port: number; +} + +interface dockerConfig { + hosts: target[]; +} +export { dockerConfig, target }; diff --git a/src/typings/frontendConfig.ts b/src/typings/frontendConfig.ts new file mode 100644 index 00000000..6ce14979 --- /dev/null +++ b/src/typings/frontendConfig.ts @@ -0,0 +1,12 @@ +interface Container { + name: string; + hidden?: boolean; + tags?: string[]; + link?: string; + icon?: string; + pinned?: boolean; +} + +type FrontendConfig = Container[]; + +export { FrontendConfig }; diff --git a/src/typings/states.ts b/src/typings/states.ts new file mode 100644 index 00000000..d5eed20b --- /dev/null +++ b/src/typings/states.ts @@ -0,0 +1,10 @@ +interface Container { + name: string; + id: string; + state: string; + hostName: string; +} + +type ContainerStates = Container[]; + +export { ContainerStates, Container }; diff --git a/src/typings/table.ts b/src/typings/table.ts new file mode 100644 index 00000000..4845ebaa --- /dev/null +++ b/src/typings/table.ts @@ -0,0 +1,7 @@ +type Table = { + id: number; // Primary key, auto-incremented + info: string; // Non-null text field + timestamp: string; // ISO 8601 formatted datetime string +}; + +export default Table; diff --git a/src/utils/connectionChecker.ts b/src/utils/connectionChecker.ts index 289b9b37..92efba3d 100644 --- a/src/utils/connectionChecker.ts +++ b/src/utils/connectionChecker.ts @@ -67,8 +67,8 @@ async function checkReachability(): Promise { const parsedData = JSON.parse(data); const hosts: Host[] = parsedData.hosts; return await checkHostStatus(hosts); - } catch (error: any) { - logger.error(`Error reading file: ${error}`); + } catch (error: unknown) { + logger.error(`Error reading file: ${error as Error}`); return undefined; } } diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts index 0cd09e39..841e9c2b 100644 --- a/src/utils/containerService.ts +++ b/src/utils/containerService.ts @@ -6,7 +6,7 @@ const configPath = "./src/data/dockerConfig.json"; interface HostConfig { name: string; - [key: string]: any; + [key: string]: string | number; } interface ContainerData { @@ -44,8 +44,8 @@ function loadConfig() { const configData = fs.readFileSync(configPath, "utf-8"); logger.debug("Loaded " + configPath); return JSON.parse(configData); - } catch (error: any) { - logger.error(`Failed to load config: ${error.message}`); + } catch (error: unknown) { + logger.error(`Failed to load config: ${(error as Error).message}`); return null; } } @@ -62,7 +62,7 @@ async function fetchAllContainers(): Promise { for (const hostConfig of config.hosts as HostConfig[]) { const hostName = hostConfig.name; try { - const docker: any = getDockerClient(hostName); + const docker = getDockerClient(hostName); logger.debug(`Now processing: ${hostName}`); const containers: ContainerInfo[] = await docker.listContainers({ all: true, @@ -103,9 +103,9 @@ async function fetchAllContainers(): Promise { current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, networkMode: containerInfo.HostConfig.NetworkMode || "unknown", }; - } catch (containerError: any) { + } catch (containerError: unknown) { logger.error( - `Error fetching details for container ID: ${container.Id} on host: ${hostName} - ${containerError.message}`, + `Error fetching details for container ID: ${container.Id} on host: ${hostName} - ${(containerError as Error).message}`, ); return { name: container.Names[0].replace("/", ""), @@ -124,12 +124,12 @@ async function fetchAllContainers(): Promise { } }), ); - } catch (error: any) { + } catch (error: unknown) { logger.error( - `Error fetching containers for host: ${hostName} - ${error.message}. Stack: ${error.stack}`, + `Error fetching containers for host: ${hostName} - ${(error as Error).message}. Stack: ${(error as Error).stack}`, ); allContainerData[hostName] = { - error: `Error fetching containers: ${error.message}`, + error: `Error fetching containers: ${(error as Error).message}`, }; } } diff --git a/src/utils/dockerClient.ts b/src/utils/dockerClient.ts index 4cb3f70c..dc0f5e91 100644 --- a/src/utils/dockerClient.ts +++ b/src/utils/dockerClient.ts @@ -19,7 +19,7 @@ function loadDockerConfig(): DockerConfig { const rawData = fs.readFileSync(configPath, "utf-8"); logger.debug("Refreshed DockerConfig.json"); return JSON.parse(rawData) as DockerConfig; - } catch (error: any) { + } catch (error: unknown) { logger.error( "Error loading dockerConfig.json: " + (error as Error).message, ); diff --git a/src/utils/notifications/_notify.ts b/src/utils/notifications/_notify.ts index 139a0066..49717f90 100644 --- a/src/utils/notifications/_notify.ts +++ b/src/utils/notifications/_notify.ts @@ -43,9 +43,8 @@ async function notify(type: string, containerId: string) { await pushoverNotification(containerId); break; default: - const errorMsg = "Unknown notification type."; - logger.error(errorMsg); - throw new Error(errorMsg); + logger.error("Unknown notification type."); + throw new Error("Unknown notification type."); } } diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index 551da826..250f0950 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -1,5 +1,6 @@ import fs from "fs"; import logger from "../logger"; +import { ContainerStates, Container } from "../../typings/states"; const templatePath: string = "./src/data/template.json"; const containersPath: string = "./src/data/states.json"; @@ -12,8 +13,8 @@ function getTemplate(): Template | null { try { const data = fs.readFileSync(templatePath, "utf8"); return JSON.parse(data); - } catch (error: any) { - logger.error("Failed to load template:", error); + } catch (error: unknown) { + logger.error("Failed to load template:", error as Error); return null; } } @@ -26,8 +27,8 @@ function setTemplate(newTemplate: string): void { "utf8", ); logger.debug("Template updated successfully"); - } catch (error: any) { - logger.error("Failed to update template:", error); + } catch (error: unknown) { + logger.error("Failed to update template:", error as Error); } } @@ -42,9 +43,11 @@ function renderTemplate(containerId: string): string | null { const data = fs.readFileSync(containersPath, "utf8"); const containers = JSON.parse(data); - let containerData: Record | null = null; + let containerData: ContainerStates | null = null; for (const host in containers) { - containerData = containers[host].find((c: any) => c.id === containerId); + containerData = containers[host].find( + (c: Container) => c.id === containerId, + ); if (containerData) { break; } @@ -56,13 +59,13 @@ function renderTemplate(containerId: string): string | null { } // Substitute placeholders in the template with container data - return Object.keys(containerData).reduce( - (text, key) => - text.replace(new RegExp(`{{${key}}}`, "g"), containerData[key]), - template.text, - ); - } catch (error: any) { - logger.error("Failed to load containers:", error); + return Object.keys(containerData).reduce((text, key) => { + const value = containerData[key as keyof ContainerStates]; + // Convert value to a string to avoid errors + return text.replace(new RegExp(`{{${key}}}`, "g"), String(value)); + }, template.text); + } catch (error: unknown) { + logger.error("Failed to load containers:", error as Error); return null; } } diff --git a/src/utils/notifications/email.ts b/src/utils/notifications/email.ts index 57c94ef9..4cd41a10 100644 --- a/src/utils/notifications/email.ts +++ b/src/utils/notifications/email.ts @@ -46,7 +46,7 @@ export async function emailNotification(containerId: string) { try { await transporter.sendMail(mailOptions); - } catch (error: any) { - logger.error("Error sending email:", error); + } catch (error: unknown) { + logger.error("Error sending email:", error as Error); } } diff --git a/src/utils/swaggerDocs.ts b/src/utils/swaggerDocs.ts index 9a386ddd..540304a7 100644 --- a/src/utils/swaggerDocs.ts +++ b/src/utils/swaggerDocs.ts @@ -1,9 +1,9 @@ import swaggerUi from "swagger-ui-express"; import swaggerJsdoc from "swagger-jsdoc"; import swaggerConfig from "../config/swaggerConfig"; -import { Express } from "express"; +import express from "express"; -const swaggerDocs = (app: Express) => { +const swaggerDocs = (app: express.Application) => { const specs = swaggerJsdoc(swaggerConfig); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs)); }; diff --git a/src/utils/writeOfflineLog.ts b/src/utils/writeOfflineLog.ts deleted file mode 100644 index 244f62e0..00000000 --- a/src/utils/writeOfflineLog.ts +++ /dev/null @@ -1,26 +0,0 @@ -import fs from "fs"; -import logger from "../utils/logger"; - -const LOG_FILE_PATH = "./logs/hostStats.json"; - -async function writeOfflineLog(message: string) { - try { - if (!fs.existsSync(LOG_FILE_PATH)) { - await fs.promises.writeFile(LOG_FILE_PATH, message); - } - } catch (error: any) { - logger.error("Error writing one time reference log: ", error); - } -} - -async function readOfflineLog() { - try { - const data = await fs.promises.readFile(LOG_FILE_PATH, "utf-8"); - logger.debug("Returning data:", data); - return data; - } catch (error: any) { - logger.error("Error reading offline log:", error); - } -} - -export { writeOfflineLog, readOfflineLog }; diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 9c800499..00000000 --- a/yarn.lock +++ /dev/null @@ -1,2852 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@apidevtools/json-schema-ref-parser@^9.0.6": - version "9.1.2" - resolved "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz" - integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== - dependencies: - "@jsdevtools/ono" "^7.1.3" - "@types/json-schema" "^7.0.6" - call-me-maybe "^1.0.1" - js-yaml "^4.1.0" - -"@apidevtools/openapi-schemas@^2.0.4": - version "2.1.0" - resolved "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz" - integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== - -"@apidevtools/swagger-methods@^3.0.2": - version "3.0.2" - resolved "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz" - integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== - -"@apidevtools/swagger-parser@10.0.3": - version "10.0.3" - resolved "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz" - integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g== - dependencies: - "@apidevtools/json-schema-ref-parser" "^9.0.6" - "@apidevtools/openapi-schemas" "^2.0.4" - "@apidevtools/swagger-methods" "^3.0.2" - "@jsdevtools/ono" "^7.1.3" - call-me-maybe "^1.0.1" - z-schema "^5.0.1" - -"@balena/dockerignore@^1.0.2": - version "1.0.2" - resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz" - integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== - -"@colors/colors@^1.6.0", "@colors/colors@1.6.0": - version "1.6.0" - resolved "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz" - integrity sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA== - -"@cspotcode/source-map-support@^0.8.0": - 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== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@dabh/diagnostics@^2.0.2": - version "2.0.3" - resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz" - integrity sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA== - dependencies: - colorspace "1.1.x" - enabled "2.0.x" - kuler "^2.0.0" - -"@esbuild/linux-x64@0.23.1": - version "0.23.1" - resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz" - integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ== - -"@gar/promisify@^1.0.1": - version "1.1.3" - resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz" - integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.2" - resolved "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz" - integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.0" - resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jsdevtools/ono@^7.1.3": - version "7.1.3" - resolved "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz" - integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== - -"@mapbox/node-pre-gyp@^1.0.11": - version "1.0.11" - resolved "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz" - integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== - dependencies: - detect-libc "^2.0.0" - https-proxy-agent "^5.0.0" - make-dir "^3.1.0" - node-fetch "^2.6.7" - nopt "^5.0.0" - npmlog "^5.0.1" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.11" - -"@npmcli/fs@^1.0.0": - version "1.1.1" - resolved "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz" - integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== - dependencies: - "@gar/promisify" "^1.0.1" - semver "^7.3.5" - -"@npmcli/move-file@^1.0.1": - version "1.1.2" - resolved "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz" - integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== - dependencies: - mkdirp "^1.0.4" - rimraf "^3.0.2" - -"@playwright/test@^1.49.0": - version "1.49.0" - resolved "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz" - integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== - dependencies: - playwright "1.49.0" - -"@scarf/scarf@=1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz" - integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.4" - resolved "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz" - integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== - -"@types/bcrypt@^5.0.2": - version "5.0.2" - resolved "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz" - integrity sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ== - dependencies: - "@types/node" "*" - -"@types/body-parser@*": - version "1.19.5" - resolved "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz" - integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.38" - resolved "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz" - integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== - dependencies: - "@types/node" "*" - -"@types/cors@^2.8.17": - version "2.8.17" - resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz" - integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== - dependencies: - "@types/node" "*" - -"@types/docker-modem@*": - version "3.0.6" - resolved "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz" - integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg== - dependencies: - "@types/node" "*" - "@types/ssh2" "*" - -"@types/dockerode@^3.3.31": - version "3.3.32" - resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz" - integrity sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg== - dependencies: - "@types/docker-modem" "*" - "@types/node" "*" - "@types/ssh2" "*" - -"@types/express-handlebars@^5.3.1": - version "5.3.1" - resolved "https://registry.npmjs.org/@types/express-handlebars/-/express-handlebars-5.3.1.tgz" - integrity sha512-DSzaERLO4gHb8AqnrL58jzSDyT0yDdl6HqDc+bGz1Hf0nrG1FK30nHGzv8NBEGR8QV9eUGB/YaE0Qj3NjF7siw== - -"@types/express-serve-static-core@^5.0.0": - version "5.0.2" - resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz" - integrity sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - "@types/send" "*" - -"@types/express@*", "@types/express@^5.0.0": - version "5.0.0" - resolved "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz" - integrity sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^5.0.0" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/http-errors@*": - version "2.0.4" - resolved "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== - -"@types/json-schema@^7.0.6": - version "7.0.15" - resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - -"@types/mime@^1": - version "1.3.5" - resolved "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz" - integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== - -"@types/node-fetch@^2.6.12": - version "2.6.12" - resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz" - integrity sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== - dependencies: - "@types/node" "*" - form-data "^4.0.0" - -"@types/node@*", "@types/node@^22.9.0": - version "22.10.1" - resolved "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz" - integrity sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ== - dependencies: - undici-types "~6.20.0" - -"@types/node@^18.11.18": - version "18.19.67" - resolved "https://registry.npmjs.org/@types/node/-/node-18.19.67.tgz" - integrity sha512-wI8uHusga+0ZugNp0Ol/3BqQfEcCCNfojtO6Oou9iVNGPTL6QNSdnUdqq85fRgIorLhLMuPIKpsN98QE9Nh+KQ== - dependencies: - undici-types "~5.26.4" - -"@types/nodemailer@^6.4.17": - version "6.4.17" - resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz" - integrity sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww== - dependencies: - "@types/node" "*" - -"@types/qs@*": - version "6.9.17" - resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz" - integrity sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ== - -"@types/range-parser@*": - version "1.2.7" - resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz" - integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== - -"@types/send@*": - version "0.17.4" - resolved "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/serve-static@*": - version "1.15.7" - resolved "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz" - integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== - dependencies: - "@types/http-errors" "*" - "@types/node" "*" - "@types/send" "*" - -"@types/ssh2@*": - version "1.15.1" - resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz" - integrity sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA== - dependencies: - "@types/node" "^18.11.18" - -"@types/supports-color@^8.1.3": - version "8.1.3" - resolved "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz" - integrity sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg== - -"@types/swagger-jsdoc@^6.0.4": - version "6.0.4" - resolved "https://registry.npmjs.org/@types/swagger-jsdoc/-/swagger-jsdoc-6.0.4.tgz" - integrity sha512-W+Xw5epcOZrF/AooUM/PccNMSAFOKWZA5dasNyMujTwsBkU74njSJBpvCCJhHAJ95XRMzQrrW844Btu0uoetwQ== - -"@types/swagger-ui-express@^4.1.7": - version "4.1.7" - resolved "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.7.tgz" - integrity sha512-ovLM9dNincXkzH4YwyYpll75vhzPBlWx6La89wwvYH7mHjVpf0X0K/vR/aUM7SRxmr5tt9z7E5XJcjQ46q+S3g== - dependencies: - "@types/express" "*" - "@types/serve-static" "*" - -"@types/triple-beam@^1.3.2": - version "1.3.5" - resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz" - integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== - -abbrev@1: - version "1.1.1" - resolved "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -accepts@~1.3.8: - 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== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-jsx-walk@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz" - integrity sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA== - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn-loose@^8.4.0: - version "8.4.0" - resolved "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.4.0.tgz" - integrity sha512-M0EUka6rb+QC4l9Z3T0nJEzNOO7JcoJlYMrBlyBCiFSXRyxjLKayd4TbQs2FDRWQU1h9FR7QVNHt+PEaoNL5rQ== - dependencies: - acorn "^8.11.0" - -acorn-walk@^8.1.1, acorn-walk@^8.3.4: - version "8.3.4" - resolved "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== - dependencies: - acorn "^8.11.0" - -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1: - version "8.14.0" - resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz" - integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== - -agent-base@^6.0.2, agent-base@6: - version "6.0.2" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -agentkeepalive@^4.1.3: - version "4.5.0" - resolved "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz" - integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== - dependencies: - humanize-ms "^1.2.1" - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - -ajv@^8.17.1: - version "8.17.1" - resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz" - integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== - dependencies: - fast-deep-equal "^3.1.3" - fast-uri "^3.0.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - -ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.3" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz" - integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -"aproba@^1.0.3 || ^2.0.0": - version "2.0.0" - resolved "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz" - integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== - -are-we-there-yet@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz" - integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -are-we-there-yet@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz" - integrity sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg== - dependencies: - delegates "^1.0.0" - readable-stream "^3.6.0" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" - integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== - -asn1@^0.2.6: - version "0.2.6" - resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -async@^3.2.3: - version "3.2.6" - resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bcrypt-pbkdf@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - -bcrypt@^5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz" - integrity sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.11" - node-addon-api "^5.0.0" - -binary-extensions@^2.0.0: - version "2.3.0" - resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" - integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== - -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - -bl@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== - dependencies: - bytes "3.1.2" - content-type "~1.0.5" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" - type-is "~1.6.18" - unpipe "1.0.0" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@~3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== - dependencies: - fill-range "^7.1.1" - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -buildcheck@~0.0.6: - version "0.0.6" - resolved "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz" - integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -cacache@^15.2.0: - version "15.3.0" - resolved "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz" - integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== - dependencies: - "@npmcli/fs" "^1.0.0" - "@npmcli/move-file" "^1.0.1" - chownr "^2.0.0" - fs-minipass "^2.0.0" - glob "^7.1.4" - infer-owner "^1.0.4" - lru-cache "^6.0.0" - minipass "^3.1.1" - minipass-collect "^1.0.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.2" - mkdirp "^1.0.3" - p-map "^4.0.0" - promise-inflight "^1.0.1" - rimraf "^3.0.2" - ssri "^8.0.1" - tar "^6.0.2" - unique-filename "^1.1.1" - -call-bind-apply-helpers@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz" - integrity sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g== - dependencies: - es-errors "^1.3.0" - function-bind "^1.1.2" - -call-bind@^1.0.7: - version "1.0.8" - resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz" - integrity sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-define-property "^1.0.0" - get-intrinsic "^1.2.4" - set-function-length "^1.2.2" - -call-me-maybe@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz" - integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== - -chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chalk@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz" - integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== - -chokidar@^3.5.2: - version "3.6.0" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" - integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.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" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz" - integrity sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA== - dependencies: - readdirp "^4.0.1" - -chownr@^1.1.1: - version "1.1.4" - resolved "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz" - integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz" - integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== - dependencies: - restore-cursor "^5.0.0" - -cli-spinners@^2.9.2: - version "2.9.2" - resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz" - integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== - -color-convert@^1.9.3: - version "1.9.3" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@^1.0.0, color-name@1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -color-string@^1.6.0: - version "1.9.1" - resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" - integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== - dependencies: - color-name "^1.0.0" - simple-swizzle "^0.2.2" - -color-support@^1.1.2, color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - -color@^3.1.3: - version "3.2.1" - resolved "https://registry.npmjs.org/color/-/color-3.2.1.tgz" - integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA== - dependencies: - color-convert "^1.9.3" - color-string "^1.6.0" - -colorspace@1.1.x: - version "1.1.4" - resolved "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz" - integrity sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w== - dependencies: - color "^3.1.3" - text-hex "1.0.x" - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^12.1.0: - version "12.1.0" - resolved "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz" - integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== - -commander@^9.4.1: - version "9.5.0" - resolved "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz" - integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== - -commander@6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz" - integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -console-control-strings@^1.0.0, console-control-strings@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz" - integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4, content-type@~1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz" - integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz" - integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== - -cookie@0.7.1: - version "0.7.1" - resolved "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz" - integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cpu-features@~0.0.10: - version "0.0.10" - resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz" - integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== - dependencies: - buildcheck "~0.0.6" - nan "^2.19.0" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -data-uri-to-buffer@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz" - integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== - -debug@^4, debug@^4.1.1, debug@^4.3.3, debug@4: - version "4.4.0" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== - dependencies: - ms "^2.1.3" - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" - integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -dependency-cruiser@^16.5.0: - version "16.7.0" - resolved "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.7.0.tgz" - integrity sha512-522LLjHINl9r0RIZ8/6s6TqIHTuEJG3XDU2WPSm9dG0rvLUYVyQwE9ID31tDFs4OOyEhdOPaqAaAG1jRv/Zwbg== - dependencies: - acorn "^8.14.0" - acorn-jsx "^5.3.2" - acorn-jsx-walk "^2.0.0" - acorn-loose "^8.4.0" - acorn-walk "^8.3.4" - ajv "^8.17.1" - commander "^12.1.0" - enhanced-resolve "^5.17.1" - ignore "^6.0.2" - interpret "^3.1.1" - is-installed-globally "^1.0.0" - json5 "^2.2.3" - memoize "^10.0.0" - picocolors "^1.1.1" - picomatch "^4.0.2" - prompts "^2.4.2" - rechoir "^0.8.0" - safe-regex "^2.1.1" - semver "^7.6.3" - teamcity-service-messages "^0.1.14" - tsconfig-paths-webpack-plugin "^4.2.0" - watskeburt "^4.1.1" - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^2.0.0: - version "2.0.3" - resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz" - integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -docker-modem@^5.0.3: - version "5.0.3" - resolved "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz" - integrity sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg== - dependencies: - debug "^4.1.1" - readable-stream "^3.5.0" - split-ca "^1.0.1" - ssh2 "^1.15.0" - -dockerode@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz" - integrity sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w== - dependencies: - "@balena/dockerignore" "^1.0.2" - docker-modem "^5.0.3" - tar-fs "~2.0.1" - -doctrine@3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dunder-proto@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz" - integrity sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A== - dependencies: - call-bind-apply-helpers "^1.0.0" - es-errors "^1.3.0" - gopd "^1.2.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" - integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== - -emoji-regex@^10.3.0: - version "10.4.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz" - integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -enabled@2.0.x: - version "2.0.0" - resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz" - integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz" - integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== - -encodeurl@~2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz" - integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== - -encoding@^0.1.0, encoding@^0.1.12: - version "0.1.13" - resolved "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - 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== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: - version "5.17.1" - resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - -err-code@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz" - integrity sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== - -es-define-property@^1.0.0, es-define-property@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz" - integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== - -es-errors@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz" - integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== - -esbuild@~0.23.0: - version "0.23.1" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz" - integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg== - optionalDependencies: - "@esbuild/aix-ppc64" "0.23.1" - "@esbuild/android-arm" "0.23.1" - "@esbuild/android-arm64" "0.23.1" - "@esbuild/android-x64" "0.23.1" - "@esbuild/darwin-arm64" "0.23.1" - "@esbuild/darwin-x64" "0.23.1" - "@esbuild/freebsd-arm64" "0.23.1" - "@esbuild/freebsd-x64" "0.23.1" - "@esbuild/linux-arm" "0.23.1" - "@esbuild/linux-arm64" "0.23.1" - "@esbuild/linux-ia32" "0.23.1" - "@esbuild/linux-loong64" "0.23.1" - "@esbuild/linux-mips64el" "0.23.1" - "@esbuild/linux-ppc64" "0.23.1" - "@esbuild/linux-riscv64" "0.23.1" - "@esbuild/linux-s390x" "0.23.1" - "@esbuild/linux-x64" "0.23.1" - "@esbuild/netbsd-x64" "0.23.1" - "@esbuild/openbsd-arm64" "0.23.1" - "@esbuild/openbsd-x64" "0.23.1" - "@esbuild/sunos-x64" "0.23.1" - "@esbuild/win32-arm64" "0.23.1" - "@esbuild/win32-ia32" "0.23.1" - "@esbuild/win32-x64" "0.23.1" - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" - integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz" - integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== - -expand-template@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz" - integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== - -express-rate-limit@^7.4.1: - version "7.4.1" - resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz" - integrity sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg== - -express@^4.21.1, "express@>=4.0.0 || >=5.0.0-beta", "express@4 || 5 || ^5.0.0-beta.1": - version "4.21.2" - resolved "https://registry.npmjs.org/express/-/express-4.21.2.tgz" - integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.3" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.7.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~2.0.0" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.3.1" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.3" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.12" - proxy-addr "~2.0.7" - qs "6.13.0" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.19.0" - serve-static "1.16.2" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -fast-deep-equal@^3.1.3: - 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-uri@^3.0.1: - version "3.0.3" - resolved "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz" - integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== - -fecha@^4.2.0: - version "4.2.3" - resolved "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz" - integrity sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw== - -fetch-blob@^3.1.2, fetch-blob@^3.1.4: - version "3.2.0" - resolved "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz" - integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ== - dependencies: - node-domexception "^1.0.0" - web-streams-polyfill "^3.0.3" - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== - dependencies: - debug "2.6.9" - encodeurl "~2.0.0" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -fn.name@1.x.x: - version "1.1.0" - resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz" - integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== - -form-data@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz" - integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -formdata-polyfill@^4.0.10: - version "4.0.10" - resolved "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz" - integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g== - dependencies: - fetch-blob "^3.1.2" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz" - integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== - -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz" - integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -gauge@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz" - integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.2" - console-control-strings "^1.0.0" - has-unicode "^2.0.1" - object-assign "^4.1.1" - signal-exit "^3.0.0" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.2" - -gauge@^4.0.3: - version "4.0.4" - resolved "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz" - integrity sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg== - dependencies: - aproba "^1.0.3 || ^2.0.0" - color-support "^1.1.3" - console-control-strings "^1.1.0" - has-unicode "^2.0.1" - signal-exit "^3.0.7" - string-width "^4.2.3" - strip-ansi "^6.0.1" - wide-align "^1.1.5" - -get-east-asian-width@^1.0.0: - version "1.3.0" - resolved "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz" - integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== - -get-intrinsic@^1.2.4: - version "1.2.5" - resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.5.tgz" - integrity sha512-Y4+pKa7XeRUPWFNvOOYHkRYrfzW07oraURSvjDmRVOJ748OrVmeXtpE4+GCEHncjCjkTxPNRt8kEbxDhsn6VTg== - dependencies: - call-bind-apply-helpers "^1.0.0" - dunder-proto "^1.0.0" - es-define-property "^1.0.1" - es-errors "^1.3.0" - function-bind "^1.1.2" - gopd "^1.2.0" - has-symbols "^1.1.0" - hasown "^2.0.2" - -get-tsconfig@^4.7.5: - version "4.8.1" - resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz" - integrity sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg== - dependencies: - resolve-pkg-maps "^1.0.0" - -github-from-package@0.0.0: - version "0.0.0" - resolved "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz" - integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob@^7.1.3, glob@^7.1.4: - version "7.2.3" - resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - 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" - -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-directory@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz" - integrity sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== - dependencies: - ini "4.1.1" - -gopd@^1.0.1, gopd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" - integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== - -graceful-fs@^4.2.4, graceful-fs@^4.2.6: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-symbols@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz" - integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== - -has-unicode@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz" - integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== - -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - -http-cache-semantics@^4.1.0: - version "4.1.1" - resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -https-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - -https@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/https/-/https-1.0.0.tgz" - integrity sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg== - -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz" - integrity sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== - dependencies: - ms "^2.0.0" - -iconv-lite@^0.6.2: - version "0.6.3" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" - integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz" - integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== - -ignore@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz" - integrity sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A== - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -infer-owner@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz" - integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@^2.0.3, inherits@^2.0.4, inherits@2, inherits@2.0.4: - version "2.0.4" - resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ini@~1.3.0: - version "1.3.8" - resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -ini@4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz" - integrity sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== - -interpret@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz" - integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== - -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" - -ipaddr.js@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz" - integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== - -ipaddr.js@1.9.1: - 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-arrayish@^0.3.1: - version "0.3.2" - resolved "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz" - integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.13.0: - version "2.15.1" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== - dependencies: - hasown "^2.0.2" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - 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-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-installed-globally@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz" - integrity sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ== - dependencies: - global-directory "^4.0.1" - is-path-inside "^4.0.0" - -is-interactive@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz" - integrity sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ== - -is-lambda@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz" - integrity sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-path-inside@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz" - integrity sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA== - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-unicode-supported@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz" - integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== - -is-unicode-supported@^2.0.0: - version "2.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz" - integrity sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ== - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - -json-schema-traverse@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz" - integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== - -json5@^2.2.2, json5@^2.2.3: - version "2.2.3" - resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" - integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -kuler@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz" - integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== - -lodash.get@^4.4.2: - version "4.4.2" - resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz" - integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== - -lodash.isequal@^4.5.0: - version "4.5.0" - resolved "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz" - integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== - -lodash.mergewith@^4.6.2: - version "4.6.2" - resolved "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz" - integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== - -log-symbols@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz" - integrity sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw== - dependencies: - chalk "^5.3.0" - is-unicode-supported "^1.3.0" - -logform@^2.7.0: - version "2.7.0" - resolved "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz" - integrity sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ== - dependencies: - "@colors/colors" "1.6.0" - "@types/triple-beam" "^1.3.2" - fecha "^4.2.0" - ms "^2.1.1" - safe-stable-stringify "^2.3.1" - triple-beam "^1.3.0" - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.1.0: - 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== - dependencies: - semver "^6.0.0" - -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -make-fetch-happen@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz" - integrity sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg== - dependencies: - agentkeepalive "^4.1.3" - cacache "^15.2.0" - http-cache-semantics "^4.1.0" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-lambda "^1.0.1" - lru-cache "^6.0.0" - minipass "^3.1.3" - minipass-collect "^1.0.2" - minipass-fetch "^1.3.2" - minipass-flush "^1.0.5" - minipass-pipeline "^1.2.4" - negotiator "^0.6.2" - promise-retry "^2.0.1" - socks-proxy-agent "^6.0.0" - ssri "^8.0.0" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz" - integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== - -memoize@^10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz" - integrity sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA== - dependencies: - mimic-function "^5.0.0" - -merge-descriptors@1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz" - integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" - integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mimic-function@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz" - integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass-collect@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz" - integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== - dependencies: - minipass "^3.0.0" - -minipass-fetch@^1.3.2: - version "1.4.1" - resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz" - integrity sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw== - dependencies: - minipass "^3.1.0" - minipass-sized "^1.0.3" - minizlib "^2.0.0" - optionalDependencies: - encoding "^0.1.12" - -minipass-flush@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz" - integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== - dependencies: - minipass "^3.0.0" - -minipass-pipeline@^1.2.2, minipass-pipeline@^1.2.4: - version "1.2.4" - resolved "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz" - integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== - dependencies: - minipass "^3.0.0" - -minipass-sized@^1.0.3: - version "1.0.3" - resolved "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz" - integrity sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g== - dependencies: - minipass "^3.0.0" - -minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.3.6" - resolved "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== - dependencies: - yallist "^4.0.0" - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== - -minizlib@^2.0.0, minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: - version "0.5.3" - resolved "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@^2.0.0, ms@^2.1.1, ms@^2.1.3, ms@2.1.3: - version "2.1.3" - resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -nan@^2.19.0, nan@^2.20.0: - version "2.22.0" - resolved "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz" - integrity sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw== - -napi-build-utils@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz" - integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== - -negotiator@^0.6.2, negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -node-abi@^3.3.0: - version "3.71.0" - resolved "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz" - integrity sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw== - dependencies: - semver "^7.3.5" - -node-addon-api@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" - integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== - -node-addon-api@^7.0.0: - version "7.1.1" - resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz" - integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== - -node-domexception@^1.0.0: - 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@^2.6.7: - version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^3.3.2: - version "3.3.2" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz" - integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA== - dependencies: - data-uri-to-buffer "^4.0.0" - fetch-blob "^3.1.4" - formdata-polyfill "^4.0.10" - -node-gyp@8.x: - version "8.4.1" - resolved "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz" - integrity sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w== - dependencies: - env-paths "^2.2.0" - glob "^7.1.4" - graceful-fs "^4.2.6" - make-fetch-happen "^9.1.0" - nopt "^5.0.0" - npmlog "^6.0.0" - rimraf "^3.0.2" - semver "^7.3.5" - tar "^6.1.2" - which "^2.0.2" - -nodemailer@^6.9.16: - version "6.9.16" - resolved "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz" - integrity sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ== - -nodemon@^3.1.7: - version "3.1.7" - resolved "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz" - integrity sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ== - dependencies: - 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" - -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npmlog@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz" - integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== - dependencies: - are-we-there-yet "^2.0.0" - console-control-strings "^1.1.0" - gauge "^3.0.0" - set-blocking "^2.0.0" - -npmlog@^6.0.0: - version "6.0.2" - resolved "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz" - integrity sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg== - dependencies: - are-we-there-yet "^3.0.0" - console-control-strings "^1.1.0" - gauge "^4.0.3" - set-blocking "^2.0.0" - -object-assign@^4, object-assign@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" - integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== - -object-inspect@^1.13.1: - version "1.13.3" - resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz" - integrity sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -one-time@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz" - integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== - dependencies: - fn.name "1.x.x" - -onetime@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz" - integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== - dependencies: - mimic-function "^5.0.0" - -openapi-types@>=7: - version "12.1.3" - resolved "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz" - integrity sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== - -ora@^8.1.1: - version "8.1.1" - resolved "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz" - integrity sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw== - dependencies: - chalk "^5.3.0" - cli-cursor "^5.0.0" - cli-spinners "^2.9.2" - is-interactive "^2.0.0" - is-unicode-supported "^2.0.0" - log-symbols "^6.0.0" - stdin-discarder "^0.2.2" - string-width "^7.2.0" - strip-ansi "^7.1.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.12: - version "0.1.12" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz" - integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== - -picocolors@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" - integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== - -picomatch@^2.0.4: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^2.2.1: - version "2.3.1" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== - -playwright-core@1.49.0: - version "1.49.0" - resolved "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz" - integrity sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA== - -playwright@1.49.0: - version "1.49.0" - resolved "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz" - integrity sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A== - dependencies: - playwright-core "1.49.0" - optionalDependencies: - fsevents "2.3.2" - -prebuild-install@^7.1.1: - version "7.1.2" - resolved "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz" - integrity sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ== - dependencies: - detect-libc "^2.0.0" - expand-template "^2.0.3" - github-from-package "0.0.0" - minimist "^1.2.3" - mkdirp-classic "^0.5.3" - napi-build-utils "^1.0.1" - node-abi "^3.3.0" - pump "^3.0.0" - rc "^1.2.7" - simple-get "^4.0.0" - tar-fs "^2.0.0" - tunnel-agent "^0.6.0" - -promise-inflight@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz" - integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== - -promise-retry@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz" - integrity sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== - dependencies: - err-code "^2.0.2" - retry "^0.12.0" - -prompts@^2.4.2: - version "2.4.2" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" - integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proxy-addr@~2.0.7: - 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== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -pump@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz" - integrity sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -qs@6.13.0: - version "6.13.0" - resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz" - integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== - dependencies: - side-channel "^1.0.6" - -range-parser@~1.2.1: - 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@2.5.2: - version "2.5.2" - resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz" - integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.2.7: - version "1.2.8" - resolved "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@^4.0.1: - version "4.0.2" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz" - integrity sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA== - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -rechoir@^0.8.0: - version "0.8.0" - resolved "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz" - integrity sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ== - dependencies: - resolve "^1.20.0" - -regexp-tree@~0.1.1: - version "0.1.27" - resolved "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz" - integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== - -require-from-string@^2.0.2: - 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-pkg-maps@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz" - integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== - -resolve@^1.20.0: - version "1.22.8" - resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -restore-cursor@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz" - integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== - dependencies: - onetime "^7.0.0" - signal-exit "^4.1.0" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz" - integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -safe-buffer@^5.0.1, safe-buffer@~5.2.0, safe-buffer@5.2.1: - 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@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz" - integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== - dependencies: - regexp-tree "~0.1.1" - -safe-stable-stringify@^2.3.1: - version "2.5.0" - resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz" - integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA== - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: - version "2.1.2" - resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^6.0.0: - version "6.3.1" - resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.5, semver@^7.5.3, semver@^7.6.3: - version "7.6.3" - resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== - -send@0.19.0: - version "0.19.0" - resolved "https://registry.npmjs.org/send/-/send-0.19.0.tgz" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.16.2: - version "1.16.2" - resolved "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz" - integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== - dependencies: - encodeurl "~2.0.0" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.19.0" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" - integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== - -set-function-length@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== - dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== - dependencies: - call-bind "^1.0.7" - es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" - -signal-exit@^3.0.0, signal-exit@^3.0.7: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - -signal-exit@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - -simple-concat@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz" - integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== - -simple-get@^4.0.0: - version "4.0.1" - resolved "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz" - integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-swizzle@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz" - integrity sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== - dependencies: - is-arrayish "^0.3.1" - -simple-update-notifier@^2.0.0: - 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== - dependencies: - semver "^7.5.3" - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -socks-proxy-agent@^6.0.0: - version "6.2.1" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz" - integrity sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ== - dependencies: - agent-base "^6.0.2" - debug "^4.3.3" - socks "^2.6.2" - -socks@^2.6.2: - version "2.8.3" - resolved "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz" - integrity sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw== - dependencies: - ip-address "^9.0.5" - smart-buffer "^4.2.0" - -split-ca@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/split-ca/-/split-ca-1.0.1.tgz" - integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== - -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sqlite3@^5.1.7: - version "5.1.7" - resolved "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz" - integrity sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog== - dependencies: - bindings "^1.5.0" - node-addon-api "^7.0.0" - prebuild-install "^7.1.1" - tar "^6.1.11" - optionalDependencies: - node-gyp "8.x" - -ssh2@^1.15.0: - version "1.16.0" - resolved "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz" - integrity sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg== - dependencies: - asn1 "^0.2.6" - bcrypt-pbkdf "^1.0.2" - optionalDependencies: - cpu-features "~0.0.10" - nan "^2.20.0" - -ssri@^8.0.0, ssri@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz" - integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== - dependencies: - minipass "^3.1.1" - -stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz" - integrity sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -stdin-discarder@^0.2.2: - version "0.2.2" - resolved "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz" - integrity sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ== - -string_decoder@^1.1.1: - 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== - dependencies: - safe-buffer "~5.2.0" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^7.2.0: - version "7.2.0" - resolved "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz" - integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== - dependencies: - emoji-regex "^10.3.0" - get-east-asian-width "^1.0.0" - strip-ansi "^7.1.0" - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^7.1.0: - version "7.1.0" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" - integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== - -supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - 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-jsdoc@^6.2.8: - version "6.2.8" - resolved "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz" - integrity sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ== - dependencies: - commander "6.2.0" - doctrine "3.0.0" - glob "7.1.6" - lodash.mergewith "^4.6.2" - swagger-parser "^10.0.3" - yaml "2.0.0-1" - -swagger-parser@^10.0.3: - version "10.0.3" - resolved "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz" - integrity sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg== - dependencies: - "@apidevtools/swagger-parser" "10.0.3" - -swagger-ui-dist@>=5.0.0: - version "5.18.2" - resolved "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz" - integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== - dependencies: - "@scarf/scarf" "=1.4.0" - -swagger-ui-express@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz" - integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== - dependencies: - swagger-ui-dist ">=5.0.0" - -tapable@^2.2.0, tapable@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -tar-fs@^2.0.0, tar-fs@~2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz" - integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== - dependencies: - chownr "^1.1.1" - mkdirp-classic "^0.5.2" - pump "^3.0.0" - tar-stream "^2.0.0" - -tar-stream@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz" - integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== - dependencies: - bl "^4.0.3" - end-of-stream "^1.4.1" - fs-constants "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.1.1" - -tar@^6.0.2, tar@^6.1.11, tar@^6.1.2: - version "6.2.1" - resolved "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== - dependencies: - 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" - -teamcity-service-messages@^0.1.14: - version "0.1.14" - resolved "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz" - integrity sha512-29aQwaHqm8RMX74u2o/h1KbMLP89FjNiMxD9wbF2BbWOnbM+q+d1sCEC+MqCc4QW3NJykn77OMpTFw/xTHIc0w== - -text-hex@1.0.x: - version "1.0.0" - resolved "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz" - integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== - -to-regex-range@^5.0.1: - 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== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -touch@^3.1.0: - version "3.1.1" - resolved "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz" - integrity sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA== - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -triple-beam@^1.3.0: - version "1.4.1" - resolved "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz" - integrity sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg== - -ts-node@^10.9.2: - version "10.9.2" - resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" - integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== - dependencies: - "@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" - -tsconfig-paths-webpack-plugin@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz" - integrity sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA== - dependencies: - chalk "^4.1.0" - enhanced-resolve "^5.7.0" - tapable "^2.2.1" - tsconfig-paths "^4.1.2" - -tsconfig-paths@^4.1.2: - version "4.2.0" - resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz" - integrity sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg== - dependencies: - json5 "^2.2.2" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tsx@^4.19.2: - version "4.19.2" - resolved "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz" - integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g== - dependencies: - esbuild "~0.23.0" - get-tsconfig "^4.7.5" - optionalDependencies: - fsevents "~2.3.3" - -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - -tweetnacl@^0.14.3: - version "0.14.5" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typescript@>=2.7: - version "5.7.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz" - integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== - -uglify-js@^3.19.3: - version "3.19.3" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz" - integrity sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ== - -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== - -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== - -unique-filename@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz" - integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== - dependencies: - unique-slug "^2.0.0" - -unique-slug@^2.0.0: - version "2.0.2" - resolved "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz" - integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== - dependencies: - imurmurhash "^0.1.4" - -unpipe@~1.0.0, unpipe@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz" - integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== - -util-deprecate@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" - integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== - -v8-compile-cache-lib@^3.0.1: - 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== - -validator@^13.7.0: - version "13.12.0" - resolved "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz" - integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" - integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== - -watskeburt@^4.1.1: - version "4.2.2" - resolved "https://registry.npmjs.org/watskeburt/-/watskeburt-4.2.2.tgz" - integrity sha512-AOCg1UYxWpiHW1tUwqpJau8vzarZYTtzl2uu99UptBmbzx6kOzCGMfRLF6KIRX4PYekmryn89MzxlRNkL66YyA== - -web-streams-polyfill@^3.0.3: - version "3.3.3" - resolved "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz" - integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.2, wide-align@^1.1.5: - version "1.1.5" - resolved "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz" - integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== - dependencies: - string-width "^1.0.2 || 2 || 3 || 4" - -winston-transport@^4.9.0: - version "4.9.0" - resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz" - integrity sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A== - dependencies: - logform "^2.7.0" - readable-stream "^3.6.2" - triple-beam "^1.3.0" - -winston@^3.15.0: - version "3.17.0" - resolved "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz" - integrity sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw== - dependencies: - "@colors/colors" "^1.6.0" - "@dabh/diagnostics" "^2.0.2" - async "^3.2.3" - is-stream "^2.0.0" - logform "^2.7.0" - one-time "^1.0.0" - readable-stream "^3.4.0" - safe-stable-stringify "^2.3.1" - stack-trace "0.0.x" - triple-beam "^1.3.0" - winston-transport "^4.9.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@2.0.0-1: - version "2.0.0-1" - resolved "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz" - integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -z-schema@^5.0.1: - version "5.0.5" - resolved "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz" - integrity sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q== - dependencies: - lodash.get "^4.4.2" - lodash.isequal "^4.5.0" - validator "^13.7.0" - optionalDependencies: - commander "^9.4.1" From ff34dff392c284cd6339d9809acbcbb68411df76 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 10:58:07 +0100 Subject: [PATCH 072/135] Fix: Added missing varaible.json file for workflow --- .github/workflows/validation.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml index d46610bc..395569fe 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yml @@ -18,6 +18,9 @@ jobs: - name: Install dependencies run: npm ci --ignore-scripts + - name: Create varaibles.json + run: npm run local-env-file + - name: Run prettier run: npm run prettier From edaab084a2fc9644354c8d6dc056832c6fe0665e Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 11:02:27 +0100 Subject: [PATCH 073/135] Chore: Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e24b1497..4e6daf38 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ _Pipelines:_
[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml)
[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml)
+[![Tests](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. With this new release a couple of extra features (compared to v1) are going to be available. From d62abc09883dbcba17d1a6d1339bc9724ef8da1e Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 11:12:56 +0100 Subject: [PATCH 074/135] Fix: Added "..." to workflow name --- .github/workflows/anchore.yml | 2 +- .github/workflows/build-dev.yaml | 2 +- .github/workflows/build-image.yml | 2 +- .github/workflows/build-test.yaml | 2 +- .github/workflows/cloc.yaml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yml index 2725a7cc..c09b20e9 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yml @@ -1,4 +1,4 @@ -name: Anchore Grype vulnerability scan +name: "Anchore Grype vulnerability scan" on: [push] diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml index f21ab4ac..62e0da98 100644 --- a/.github/workflows/build-dev.yaml +++ b/.github/workflows/build-dev.yaml @@ -1,4 +1,4 @@ -name: Build dockstatapi:nightly +name: "Build dockstatapi:nightly" on: push: diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 17933f97..9e382a29 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,4 +1,4 @@ -name: Build dockstatapi:latest +name: "Build dockstatapi:latest" on: release: diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 2f2322f5..631af23e 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -1,4 +1,4 @@ -name: Build test docker image +name: "Build test docker image" on: [push] diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index d29afa4a..004f51b6 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -1,4 +1,4 @@ -name: Count Lines of Code +name: "Count Lines of Code" permissions: issues: write From 569044d224c4f98b2d1c05799f4f125750053806 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:11:10 +0100 Subject: [PATCH 075/135] Feat: Add better workflow logic --- .github/workflows/{anchore.yml => anchore.yaml} | 3 ++- .github/workflows/{build-image.yml => build-image.yaml} | 0 .github/workflows/build-test.yaml | 3 ++- .github/workflows/{codeql.yml => codeql.yaml} | 7 +------ .../workflows/{remove-stale.yml => remove-stale.yaml} | 0 .github/workflows/{validation.yml => validation.yaml} | 9 +++++++++ 6 files changed, 14 insertions(+), 8 deletions(-) rename .github/workflows/{anchore.yml => anchore.yaml} (97%) rename .github/workflows/{build-image.yml => build-image.yaml} (100%) rename .github/workflows/{codeql.yml => codeql.yaml} (91%) rename .github/workflows/{remove-stale.yml => remove-stale.yaml} (100%) rename .github/workflows/{validation.yml => validation.yaml} (75%) diff --git a/.github/workflows/anchore.yml b/.github/workflows/anchore.yaml similarity index 97% rename from .github/workflows/anchore.yml rename to .github/workflows/anchore.yaml index c09b20e9..290694c3 100644 --- a/.github/workflows/anchore.yml +++ b/.github/workflows/anchore.yaml @@ -1,6 +1,7 @@ name: "Anchore Grype vulnerability scan" -on: [push] +on: + workflow_call: permissions: contents: read diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yaml similarity index 100% rename from .github/workflows/build-image.yml rename to .github/workflows/build-image.yaml diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 631af23e..a87c5d8a 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -1,6 +1,7 @@ name: "Build test docker image" -on: [push] +on: + workflow_call: permissions: packages: write diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yaml similarity index 91% rename from .github/workflows/codeql.yml rename to .github/workflows/codeql.yaml index 081205c6..d0eec671 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yaml @@ -1,12 +1,7 @@ name: "CodeQL Advanced" on: - push: - branches: ["main"] - pull_request: - branches: ["main"] - schedule: - - cron: "32 1 * * 5" + workflow_call: jobs: codeql: diff --git a/.github/workflows/remove-stale.yml b/.github/workflows/remove-stale.yaml similarity index 100% rename from .github/workflows/remove-stale.yml rename to .github/workflows/remove-stale.yaml diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yaml similarity index 75% rename from .github/workflows/validation.yml rename to .github/workflows/validation.yaml index 395569fe..05f2377f 100644 --- a/.github/workflows/validation.yml +++ b/.github/workflows/validation.yaml @@ -32,3 +32,12 @@ jobs: - name: Audit packages run: npm audit --audit-level=high + + - name: Run Anchore + uses: ./.github/workflows/anchore.yml + + - name: Run CodeQL + uses: ./.github/workflows/codeql.yml + + - name: Test build + uses: ./.github/workflows/build-test.yaml From 8183c6a648bd1712d1caf924b85d8923b3072eca Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:12:33 +0100 Subject: [PATCH 076/135] Fix: Renamed files => adjusted in code --- .github/workflows/validation.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 05f2377f..eec6609f 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -34,10 +34,10 @@ jobs: run: npm audit --audit-level=high - name: Run Anchore - uses: ./.github/workflows/anchore.yml + uses: ./.github/workflows/anchore.yaml - name: Run CodeQL - uses: ./.github/workflows/codeql.yml + uses: ./.github/workflows/codeql.yaml - name: Test build uses: ./.github/workflows/build-test.yaml From c646f0e349866a252d681894c785763a08adc1ac Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:23:18 +0100 Subject: [PATCH 077/135] Fix: Workflows depending on each other (test) --- .github/workflows/anchore.yaml | 10 +++++++--- .github/workflows/build-test.yaml | 7 ++++++- .github/workflows/codeql.yaml | 10 +++++++--- .github/workflows/validation.yaml | 6 ------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/workflows/anchore.yaml b/.github/workflows/anchore.yaml index 290694c3..24ce2186 100644 --- a/.github/workflows/anchore.yaml +++ b/.github/workflows/anchore.yaml @@ -1,7 +1,11 @@ -name: "Anchore Grype vulnerability scan" +name: "Anchore Grype Vulnerability Scan" on: - workflow_call: + workflow_run: + workflows: + - "Run all tests" # Replace with the actual name of the preceding workflow + types: + - completed permissions: contents: read @@ -16,7 +20,7 @@ jobs: - name: Download Grype run: | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin - - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + - uses: actions/checkout@v4 - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest - name: Run Grype test diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a87c5d8a..5113169e 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -1,7 +1,12 @@ name: "Build test docker image" on: - workflow_call: + workflow_run: + workflows: + - "Anchore Grype Vulnerability Scan" + - "CodeQL Advanced" + types: + - completed permissions: packages: write diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index d0eec671..7ba4587a 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -1,12 +1,16 @@ name: "CodeQL Advanced" on: - workflow_call: + workflow_run: + workflows: + - "Anchore Grype Vulnerability Scan" + types: + - completed jobs: codeql: name: Analyze TypeScript - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest permissions: security-events: write packages: read @@ -44,4 +48,4 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: - category: "/language:${{matrix.language}}" + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index eec6609f..3c6f2cec 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -33,11 +33,5 @@ jobs: - name: Audit packages run: npm audit --audit-level=high - - name: Run Anchore - uses: ./.github/workflows/anchore.yaml - - - name: Run CodeQL - uses: ./.github/workflows/codeql.yaml - - name: Test build uses: ./.github/workflows/build-test.yaml From 19a1d242060aec391aa92e863cd404add8c7c178 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:24:55 +0100 Subject: [PATCH 078/135] Fix: Remove old 'residue' of workflow files --- .github/workflows/validation.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 3c6f2cec..395569fe 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -32,6 +32,3 @@ jobs: - name: Audit packages run: npm audit --audit-level=high - - - name: Test build - uses: ./.github/workflows/build-test.yaml From 16b0aa90019c245666524b6780d6d3a2b7736862 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:33:42 +0100 Subject: [PATCH 079/135] Fix: Adjust workflows --- .github/workflows/anchore.yaml | 8 +++ .github/workflows/build-test.yaml | 13 +++++ .github/workflows/codeql.yaml | 3 ++ CREDITS.md | 73 ++++++++++++-------------- package.json | 2 +- src/controllers/containerController.ts | 8 ++- 6 files changed, 62 insertions(+), 45 deletions(-) diff --git a/.github/workflows/anchore.yaml b/.github/workflows/anchore.yaml index 24ce2186..436ba6bd 100644 --- a/.github/workflows/anchore.yaml +++ b/.github/workflows/anchore.yaml @@ -17,15 +17,23 @@ jobs: steps: - name: Set up Grype installation path run: echo "$HOME/bin" >> $GITHUB_PATH + - name: Download Grype run: | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin + - uses: actions/checkout@v4 + - name: Build the Container image run: docker build . --file Dockerfile --tag localbuild/testimage:latest + - name: Run Grype test run: grype -o sarif localbuild/testimage:latest > results.sarif + - name: Upload Anchore scan SARIF report uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ./results.sarif + + - name: Set Marker for Workflow Completion + run: echo "anchore_complete=true" >> $GITHUB_ENV diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index 5113169e..a0f6db26 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -16,6 +16,19 @@ jobs: build-test: runs-on: ubuntu-latest steps: + - name: Check workflow dependencies + run: | + for i in {1..10}; do + if [[ "${{ env.anchore_complete }}" == "true" && "${{ env.codeql_complete }}" == "true" ]]; then + echo "All workflows complete!" + exit 0 + fi + echo "Dependencies not yet complete. Retrying in 60 seconds..." + sleep 60 + done + echo "Dependencies not met within the timeout period. Exiting." + exit 1 + - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 7ba4587a..4e2f5f92 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -49,3 +49,6 @@ jobs: uses: github/codeql-action/analyze@v3 with: category: "/language:${{ matrix.language }}" + + - name: Set Marker for Workflow Completion + run: echo "codeql_complete=true" >> $GITHUB_ENV diff --git a/CREDITS.md b/CREDITS.md index be34b479..050b430b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -4,53 +4,48 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| ----------------- | -------------------------------------------- | -------------------- | | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | - ### License: Apache-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | - +| Name | Repository | Publisher | +| ------------------------------------ | ------------------------------------------------------------- | --------------------- | +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | ### License: CC-BY-3.0 -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| --------------------- | -------------------------------------------------- | -------------------- | | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | - ### License: Python-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | - - +| Name | Repository | Publisher | +| -------------- | ---------------------------------- | --------- | +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | diff --git a/package.json b/package.json index 9acd9525..90422a28 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", "docker:build": "docker build . -t \"dockstatapi:local\" -f ./Dockerfile-dev && docker compose up -d", "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", - "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.{yaml,yml} --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", + "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.yaml --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", "lint": "npx eslint", "lint:fix": "npx eslint --fix", "license": "bash ./src/misc/credits.sh", diff --git a/src/controllers/containerController.ts b/src/controllers/containerController.ts index 8d3bef30..ef1c8cef 100644 --- a/src/controllers/containerController.ts +++ b/src/controllers/containerController.ts @@ -40,11 +40,9 @@ const getContainerStats = async ( logger.error( `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${(error as Error).message}`, ); - res - .status(500) - .json({ - error: `Error fetching container stats: ${(error as Error).message}`, - }); + res.status(500).json({ + error: `Error fetching container stats: ${(error as Error).message}`, + }); } }; From 2766aa626e0f8243e89a508816b2e9b9c13fb687 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:49:11 +0100 Subject: [PATCH 080/135] Fix: Adjust workflows (hopefully) --- .github/workflows/anchore.yaml | 6 +----- .github/workflows/build-test.yaml | 20 +------------------- .github/workflows/codeql.yaml | 6 +----- .github/workflows/validation.yaml | 17 +++++++++++++++++ 4 files changed, 20 insertions(+), 29 deletions(-) diff --git a/.github/workflows/anchore.yaml b/.github/workflows/anchore.yaml index 436ba6bd..7aa91629 100644 --- a/.github/workflows/anchore.yaml +++ b/.github/workflows/anchore.yaml @@ -1,11 +1,7 @@ name: "Anchore Grype Vulnerability Scan" on: - workflow_run: - workflows: - - "Run all tests" # Replace with the actual name of the preceding workflow - types: - - completed + workflow_call: permissions: contents: read diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a0f6db26..a87c5d8a 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -1,12 +1,7 @@ name: "Build test docker image" on: - workflow_run: - workflows: - - "Anchore Grype Vulnerability Scan" - - "CodeQL Advanced" - types: - - completed + workflow_call: permissions: packages: write @@ -16,19 +11,6 @@ jobs: build-test: runs-on: ubuntu-latest steps: - - name: Check workflow dependencies - run: | - for i in {1..10}; do - if [[ "${{ env.anchore_complete }}" == "true" && "${{ env.codeql_complete }}" == "true" ]]; then - echo "All workflows complete!" - exit 0 - fi - echo "Dependencies not yet complete. Retrying in 60 seconds..." - sleep 60 - done - echo "Dependencies not met within the timeout period. Exiting." - exit 1 - - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml index 4e2f5f92..c187b095 100644 --- a/.github/workflows/codeql.yaml +++ b/.github/workflows/codeql.yaml @@ -1,11 +1,7 @@ name: "CodeQL Advanced" on: - workflow_run: - workflows: - - "Anchore Grype Vulnerability Scan" - types: - - completed + workflow_call: jobs: codeql: diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 395569fe..1918efdc 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -32,3 +32,20 @@ jobs: - name: Audit packages run: npm audit --audit-level=high + + code-testing: + needs: validation + runs-on: ubuntu-latest + steps: + - name: CodeQL + uses: ./.github/workflows/codeql.yaml + + - name: Anchore + uses: ./.github/workflows/anchore.yaml + + test-building: + needs: code-testing + runs-on: ubuntu-latest + steps: + - name: Test build + uses: ./.github/workflows/build-test.yaml From 406fe0cfd7990a807e5fec8e9bba16a27c387205 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 16:52:21 +0100 Subject: [PATCH 081/135] Fix: More adjustments --- .github/workflows/validation.yaml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 1918efdc..9a6387ef 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -33,18 +33,28 @@ jobs: - name: Audit packages run: npm audit --audit-level=high - code-testing: + CodeQL: needs: validation runs-on: ubuntu-latest steps: + - name: Checkout + uses: actions/checkout@v4 + - name: CodeQL uses: ./.github/workflows/codeql.yaml + Anchore: + needs: validation + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Anchore uses: ./.github/workflows/anchore.yaml test-building: - needs: code-testing + needs: [CodeQL, Anchore] runs-on: ubuntu-latest steps: - name: Test build From 25696d8f44d726e495d31a256ed1ef41ee98548d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 17:08:37 +0100 Subject: [PATCH 082/135] Fix: Move workflow files around --- .github/workflows/anchore.yaml | 35 ---------- .github/workflows/build-test.yaml | 45 ------------ .github/workflows/codeql.yaml | 50 -------------- .github/workflows/validation.yaml | 109 +++++++++++++++++++++++++++--- 4 files changed, 100 insertions(+), 139 deletions(-) delete mode 100644 .github/workflows/anchore.yaml delete mode 100644 .github/workflows/codeql.yaml diff --git a/.github/workflows/anchore.yaml b/.github/workflows/anchore.yaml deleted file mode 100644 index 7aa91629..00000000 --- a/.github/workflows/anchore.yaml +++ /dev/null @@ -1,35 +0,0 @@ -name: "Anchore Grype Vulnerability Scan" - -on: - workflow_call: - -permissions: - contents: read - security-events: write - -jobs: - anchore: - runs-on: ubuntu-latest - steps: - - name: Set up Grype installation path - run: echo "$HOME/bin" >> $GITHUB_PATH - - - name: Download Grype - run: | - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin - - - uses: actions/checkout@v4 - - - name: Build the Container image - run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - - name: Run Grype test - run: grype -o sarif localbuild/testimage:latest > results.sarif - - - name: Upload Anchore scan SARIF report - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: ./results.sarif - - - name: Set Marker for Workflow Completion - run: echo "anchore_complete=true" >> $GITHUB_ENV diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml index a87c5d8a..5ea74362 100644 --- a/.github/workflows/build-test.yaml +++ b/.github/workflows/build-test.yaml @@ -10,48 +10,3 @@ permissions: jobs: build-test: runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - # Set up Node.js using nvm - - name: Set up Node.js version from .nvmrc - run: | - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash - export NVM_DIR="$HOME/.nvm" - [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - nvm install - nvm use - node -v - npm -v - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate Docker tags - uses: docker/metadata-action@v5 - id: metadata - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} - - - name: Build and Push Docker Images - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64 - push: false - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml deleted file mode 100644 index c187b095..00000000 --- a/.github/workflows/codeql.yaml +++ /dev/null @@ -1,50 +0,0 @@ -name: "CodeQL Advanced" - -on: - workflow_call: - -jobs: - codeql: - name: Analyze TypeScript - runs-on: ubuntu-latest - permissions: - security-events: write - packages: read - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: javascript-typescript - build-mode: none - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - queries: security-extended - - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{ matrix.language }}" - - - name: Set Marker for Workflow Completion - run: echo "codeql_complete=true" >> $GITHUB_ENV diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 9a6387ef..a0154c42 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -36,26 +36,117 @@ jobs: CodeQL: needs: validation runs-on: ubuntu-latest + name: Analyze TypeScript + permissions: + security-events: write + packages: read + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: javascript-typescript + build-mode: none + steps: - - name: Checkout + - name: Checkout repository uses: actions/checkout@v4 - - name: CodeQL - uses: ./.github/workflows/codeql.yaml + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-extended + + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" + + - name: Set Marker for Workflow Completion + run: echo "codeql_complete=true" >> $GITHUB_ENV Anchore: needs: validation runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v4 + - name: Set up Grype installation path + run: echo "$HOME/bin" >> $GITHUB_PATH + + - name: Download Grype + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin + + - uses: actions/checkout@v4 + + - name: Build the Container image + run: docker build . --file Dockerfile --tag localbuild/testimage:latest - - name: Anchore - uses: ./.github/workflows/anchore.yaml + - name: Run Grype test + run: grype -o sarif localbuild/testimage:latest > results.sarif + + - name: Upload Anchore scan SARIF report + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: ./results.sarif test-building: needs: [CodeQL, Anchore] runs-on: ubuntu-latest steps: - - name: Test build - uses: ./.github/workflows/build-test.yaml + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js version from .nvmrc + run: | + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash + export NVM_DIR="$HOME/.nvm" + [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" + nvm install + nvm use + node -v + npm -v + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} + + - name: Build and Push Docker Images + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + push: false + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 7a4134fdefa731817cb5669b8331f6dd776f29a8 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 17:15:48 +0100 Subject: [PATCH 083/135] Fix: Fixing permissions --- .github/workflows/validation.yaml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index a0154c42..6bc15507 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -5,6 +5,11 @@ on: [push] jobs: validation: runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + actions: read + contents: read steps: - name: Checkout uses: actions/checkout@v4 @@ -76,12 +81,14 @@ jobs: with: category: "/language:${{ matrix.language }}" - - name: Set Marker for Workflow Completion - run: echo "codeql_complete=true" >> $GITHUB_ENV - Anchore: needs: validation runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + actions: read + contents: read steps: - name: Set up Grype installation path run: echo "$HOME/bin" >> $GITHUB_PATH @@ -106,6 +113,11 @@ jobs: test-building: needs: [CodeQL, Anchore] runs-on: ubuntu-latest + permissions: + security-events: write + packages: read + actions: read + contents: read steps: - name: Checkout repository uses: actions/checkout@v4 From 61a49457e7e47f9ca45b00253209711adfece650 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 17:26:17 +0100 Subject: [PATCH 084/135] Feat: Added workflow naming --- .github/workflows/build-test.yaml | 12 ------------ .github/workflows/validation.yaml | 6 +++++- 2 files changed, 5 insertions(+), 13 deletions(-) delete mode 100644 .github/workflows/build-test.yaml diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml deleted file mode 100644 index 5ea74362..00000000 --- a/.github/workflows/build-test.yaml +++ /dev/null @@ -1,12 +0,0 @@ -name: "Build test docker image" - -on: - workflow_call: - -permissions: - packages: write - contents: read - -jobs: - build-test: - runs-on: ubuntu-latest diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 6bc15507..9d771ad8 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -5,6 +5,7 @@ on: [push] jobs: validation: runs-on: ubuntu-latest + name: Validation permissions: security-events: write packages: read @@ -66,7 +67,8 @@ jobs: build-mode: ${{ matrix.build-mode }} queries: security-extended - - if: matrix.build-mode == 'manual' + - name: Check build mode + if: matrix.build-mode == 'manual' shell: bash run: | echo 'If you are using a "manual" build mode for one or more of the' \ @@ -84,6 +86,7 @@ jobs: Anchore: needs: validation runs-on: ubuntu-latest + name: Anchore permissions: security-events: write packages: read @@ -113,6 +116,7 @@ jobs: test-building: needs: [CodeQL, Anchore] runs-on: ubuntu-latest + name: Test building permissions: security-events: write packages: read From ee37b8071e2393db94fa03208b25913ef82eeeff Mon Sep 17 00:00:00 2001 From: ItsNik Date: Mon, 30 Dec 2024 17:43:09 +0100 Subject: [PATCH 085/135] Fix: Preparing for yet another workflow change... --- .github/workflows/validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 9d771ad8..5d6e8d38 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -114,7 +114,7 @@ jobs: sarif_file: ./results.sarif test-building: - needs: [CodeQL, Anchore] + needs: validation runs-on: ubuntu-latest name: Test building permissions: From a63c4b0258b9d62efc063578c2e455591bdfa677 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 31 Dec 2024 15:05:31 +0100 Subject: [PATCH 086/135] Feat: New workflow structure for dev build --- .github/workflows/build-dev.yaml | 47 ----------------------------- .github/workflows/validation.yaml | 49 +++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/build-dev.yaml diff --git a/.github/workflows/build-dev.yaml b/.github/workflows/build-dev.yaml deleted file mode 100644 index 62e0da98..00000000 --- a/.github/workflows/build-dev.yaml +++ /dev/null @@ -1,47 +0,0 @@ -name: "Build dockstatapi:nightly" - -on: - push: - branches: - - "dev" - -permissions: - packages: write - contents: read - -jobs: - build-dev: - runs-on: ubuntu-latest - steps: - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ github.token }} - - - name: Generate Docker tags - uses: docker/metadata-action@v5 - id: metadata - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=nightly - flavor: | - latest=false - - - name: Build and push - uses: docker/build-push-action@v6 - with: - platforms: linux/amd64,linux/arm64, - push: true - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 5d6e8d38..501ac9c3 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -5,7 +5,7 @@ on: [push] jobs: validation: runs-on: ubuntu-latest - name: Validation + name: "Validation" permissions: security-events: write packages: read @@ -42,7 +42,7 @@ jobs: CodeQL: needs: validation runs-on: ubuntu-latest - name: Analyze TypeScript + name: "Analyze TypeScript" permissions: security-events: write packages: read @@ -86,7 +86,7 @@ jobs: Anchore: needs: validation runs-on: ubuntu-latest - name: Anchore + name: "Anchore" permissions: security-events: write packages: read @@ -114,9 +114,9 @@ jobs: sarif_file: ./results.sarif test-building: - needs: validation + needs: [validation, Anchore, CodeQL] runs-on: ubuntu-latest - name: Test building + name: "Test building" permissions: security-events: write packages: read @@ -166,3 +166,42 @@ jobs: labels: ${{ steps.metadata.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + + build-dev: + name: "Dev-build" + runs-on: ubuntu-latest + if: github.ref_name == 'dev' + needs: [validation, test-building, Anchore, CodeQL] + steps: + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,enable=true,priority=200,prefix=,suffix=,value=nightly + flavor: | + latest=false + + - name: Build and push + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64, + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 3bf8da473822f54eacef67083052f54f589712dc Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 31 Dec 2024 15:10:09 +0100 Subject: [PATCH 087/135] Fix: permissions --- .github/workflows/validation.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 501ac9c3..dd7357ca 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -169,6 +169,11 @@ jobs: build-dev: name: "Dev-build" + permissions: + security-events: read + packages: write + actions: read + contents: read runs-on: ubuntu-latest if: github.ref_name == 'dev' needs: [validation, test-building, Anchore, CodeQL] From 687e44aa0320d169d6a2a11280872228d58fbc55 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 31 Dec 2024 15:14:50 +0100 Subject: [PATCH 088/135] Feat: Update to ubuntu-24.04 in GHA --- .github/workflows/build-image.yaml | 2 +- .github/workflows/cloc.yaml | 2 +- .github/workflows/remove-stale.yaml | 2 +- .github/workflows/validation.yaml | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 9e382a29..720bed85 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -10,7 +10,7 @@ permissions: jobs: build-release: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/cloc.yaml b/.github/workflows/cloc.yaml index 004f51b6..0f4245f9 100644 --- a/.github/workflows/cloc.yaml +++ b/.github/workflows/cloc.yaml @@ -10,7 +10,7 @@ on: jobs: cloc: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/remove-stale.yaml b/.github/workflows/remove-stale.yaml index 93d1acdc..47f9ae24 100644 --- a/.github/workflows/remove-stale.yaml +++ b/.github/workflows/remove-stale.yaml @@ -5,7 +5,7 @@ on: jobs: remove-stale: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - uses: actions/stale@v9 with: diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index dd7357ca..1abe640a 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -4,7 +4,7 @@ on: [push] jobs: validation: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: "Validation" permissions: security-events: write @@ -41,7 +41,7 @@ jobs: CodeQL: needs: validation - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: "Analyze TypeScript" permissions: security-events: write @@ -85,7 +85,7 @@ jobs: Anchore: needs: validation - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: "Anchore" permissions: security-events: write @@ -115,7 +115,7 @@ jobs: test-building: needs: [validation, Anchore, CodeQL] - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 name: "Test building" permissions: security-events: write @@ -174,7 +174,7 @@ jobs: packages: write actions: read contents: read - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 if: github.ref_name == 'dev' needs: [validation, test-building, Anchore, CodeQL] steps: From dc2fed835e24c68d3fe72f70f3be306ace28a754 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 01:17:16 +0100 Subject: [PATCH 089/135] Feat: Custom User for Docker container --- Dockerfile | 25 ++++++++++++------------- Dockerfile-dev | 25 ++++++++++++------------- docker-compose.yaml | 5 ++++- src/misc/createEnvFile.sh | 0 src/misc/credits.sh | 0 src/misc/entrypoint.sh | 3 ++- src/misc/minifyDist.sh | 0 7 files changed, 30 insertions(+), 28 deletions(-) mode change 100644 => 100755 src/misc/createEnvFile.sh mode change 100644 => 100755 src/misc/credits.sh mode change 100644 => 100755 src/misc/minifyDist.sh diff --git a/Dockerfile b/Dockerfile index dc4f58cf..0e59a459 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,10 +14,7 @@ LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" WORKDIR /build ENV NODE_NO_WARNINGS=1 -RUN apk update && \ - apk upgrade && \ - apk add bash - +RUN apk add --update --no-cache bash COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install @@ -29,16 +26,13 @@ RUN npm run build:mini # Stage 2: main stage FROM alpine AS main -# Needed packages -RUN apk update && \ - apk upgrade && \ - apk add --update npm +RUN apk add --update npm WORKDIR /build RUN mkdir -p /build/src/data -COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ +COPY package*.json ./ RUN npm install --omit=dev COPY --from=builder /build/dist/* /build/src @@ -50,13 +44,18 @@ RUN node src/config/db.js # Stage 3: Production stage FROM alpine AS production -RUN apk add --update bash curl nodejs +WORKDIR /api + +RUN apk add --update --no-cache bash curl nodejs && \ + adduser -h /api -s /bin/bash -D dockstatapi dockstatapi && \ + chown -hR dockstatapi:dockstatapi /api + +USER dockstatapi + HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost:9876/api/status || exit 1 -WORKDIR /api - -COPY --from=main /build /api +COPY --chown=dockstatapi:dockstatapi --from=main /build /api EXPOSE 9876 ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/Dockerfile-dev b/Dockerfile-dev index bd246884..ba9c01cb 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -14,10 +14,7 @@ LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" WORKDIR /build ENV NODE_NO_WARNINGS=1 -RUN apk update && \ - apk upgrade && \ - apk add bash - +RUN apk add --update --no-cache bash COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ RUN npm install @@ -29,16 +26,13 @@ RUN npm run build # Stage 2: main stage FROM alpine AS main -# Needed packages -RUN apk update && \ - apk upgrade && \ - apk add --update npm +RUN apk add --update npm WORKDIR /build RUN mkdir -p /build/src/data -COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ +COPY package*.json ./ RUN npm install --omit=dev COPY --from=builder /build/dist/* /build/src @@ -50,13 +44,18 @@ RUN node src/config/db.js # Stage 3: Production stage FROM alpine AS production -RUN apk add --update bash curl nodejs +WORKDIR /api + +RUN apk add --update --no-cache bash curl nodejs && \ + adduser -h /api -s /bin/bash -D dockstatapi dockstatapi && \ + chown -hR dockstatapi:dockstatapi /api + +USER dockstatapi + HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost:9876/api/status || exit 1 -WORKDIR /api - -COPY --from=main /build /api +COPY --chown=dockstatapi:dockstatapi --from=main /build /api EXPOSE 9876 ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/docker-compose.yaml b/docker-compose.yaml index 06d1f459..4789b71c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,7 +7,7 @@ services: container_name: master environment: - NODE_ENV=development - - HA_MASTER=true + - HA_MASTER=false - HA_MASTER_IP=master:9876 - HA_NODE=slave:9876 - HA_UNSAFE=true @@ -21,6 +21,7 @@ services: depends_on: - slave - test-socket-proxy + slave: container_name: slave environment: @@ -30,6 +31,8 @@ services: ports: - 6789:9876 image: dockstatapi:local + depends_on: + - test-socket-proxy networks: - shared-network diff --git a/src/misc/createEnvFile.sh b/src/misc/createEnvFile.sh old mode 100644 new mode 100755 diff --git a/src/misc/credits.sh b/src/misc/credits.sh old mode 100644 new mode 100755 diff --git a/src/misc/entrypoint.sh b/src/misc/entrypoint.sh index 83eaf46d..60b8a0e4 100755 --- a/src/misc/entrypoint.sh +++ b/src/misc/entrypoint.sh @@ -1,3 +1,4 @@ +# entrypoint.sh: #!/bin/bash VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" @@ -24,6 +25,6 @@ DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simpl " -bash "./createEnvFile.sh" +bash ./createEnvFile.sh exec node src/server.js diff --git a/src/misc/minifyDist.sh b/src/misc/minifyDist.sh old mode 100644 new mode 100755 From 1f59fd4b5428f4151b87b93e7c484247ca178fd3 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 01:57:41 +0100 Subject: [PATCH 090/135] Fix: Lock acquisition needs exponential backoff to prevent thundering herd Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> --- src/controllers/highAvailability.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index 7bf7dc77..20e33de7 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -47,10 +47,25 @@ const configFiles: string[] = [ "./src/data/password.json", ]; +const MAX_RETRIES = 10; +const BASE_DELAY_MS = 100; + async function acquireLock(): Promise { + let retryCount = 0; + while (fs.existsSync(lockFilePath)) { - logger.warn("Lock file exists, waiting..."); - await sleep(100); + if (retryCount >= MAX_RETRIES) { + throw new Error("Failed to acquire lock: maximum retry attempts exceeded"); + } + + const backoffMs = BASE_DELAY_MS * Math.pow(2, retryCount); + // Add jitter to prevent thundering herd + const jitter = Math.random() * 0.3 * backoffMs; + const delayMs = backoffMs + jitter; + + logger.warn(`Lock file exists, waiting ${Math.round(delayMs)}ms before retry ${retryCount + 1}/${MAX_RETRIES}...`); + await sleep(delayMs); + retryCount++; } try { From a18b9d47158caa19617081c4ce69e4c4d49c758c Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 02:00:48 +0100 Subject: [PATCH 091/135] Feat: Daily rotating log files --- .github/workflows/validation.yaml | 4 +-- package-lock.json | 48 ++++++++++++++++++++++++++++++- package.json | 3 +- src/config/loggerConfig.ts | 9 +++++- 4 files changed, 59 insertions(+), 5 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 1abe640a..dfd9330a 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -63,7 +63,7 @@ jobs: - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: - languages: ${{ matrix.language }} + languages: javascript-typescript build-mode: ${{ matrix.build-mode }} queries: security-extended @@ -81,7 +81,7 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: - category: "/language:${{ matrix.language }}" + category: "/language:javascript-typescript" Anchore: needs: validation diff --git a/package-lock.json b/package-lock.json index ba55c01c..1310ab8b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,8 @@ "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "winston": "^3.15.0" + "winston": "^3.15.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -3031,6 +3032,15 @@ "node": ">=16.0.0" } }, + "node_modules/file-stream-rotator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz", + "integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.1" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -4512,6 +4522,15 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "license": "MIT" }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4869,6 +4888,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", @@ -6980,6 +7008,24 @@ "node": ">= 12.0.0" } }, + "node_modules/winston-daily-rotate-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz", + "integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==", + "license": "MIT", + "dependencies": { + "file-stream-rotator": "^0.6.1", + "object-hash": "^3.0.0", + "triple-beam": "^1.4.1", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "winston": "^3" + } + }, "node_modules/winston-transport": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", diff --git a/package.json b/package.json index 90422a28..6dd43aba 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,8 @@ "sqlite3": "^5.1.7", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", - "winston": "^3.15.0" + "winston": "^3.15.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@eslint/js": "^9.17.0", diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts index 5d1a33e4..f2b30f43 100644 --- a/src/config/loggerConfig.ts +++ b/src/config/loggerConfig.ts @@ -1,4 +1,5 @@ import { createLogger, format, transports } from "winston"; +import DailyRotateFile from "winston-daily-rotate-file"; const gray = "\x1b[90m"; const reset = "\x1b[0m"; @@ -49,7 +50,13 @@ const logger = createLogger({ ), transports: [ new transports.Console(), - new transports.File({ filename: "logs/app.log" }), + new DailyRotateFile({ + filename: "logs/app-%DATE%.log", + datePattern: "YYYY-MM-DD", + maxSize: "20m", + maxFiles: "14d", + zippedArchive: true, + }), ], }); From a6838b08fe1442b624caa6466a9f00063ab32794 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 02:01:49 +0100 Subject: [PATCH 092/135] Feat: Better locking --- CREDITS.md | 73 +++++++++++++++-------------- src/controllers/highAvailability.ts | 8 +++- 2 files changed, 45 insertions(+), 36 deletions(-) diff --git a/CREDITS.md b/CREDITS.md index 050b430b..be34b479 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -4,48 +4,53 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) -| Name | Repository | Publisher | -| ----------------- | -------------------------------------------- | -------------------- | +| Name | Repository | Publisher | +|------|-------------|-----------| | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | + ### License: Apache-2.0 -| Name | Repository | Publisher | -| ------------------------------------ | ------------------------------------------------------------- | --------------------- | -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | +| Name | Repository | Publisher | +|------|-------------|-----------| +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | + ### License: CC-BY-3.0 -| Name | Repository | Publisher | -| --------------------- | -------------------------------------------------- | -------------------- | +| Name | Repository | Publisher | +|------|-------------|-----------| | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | + ### License: Python-2.0 -| Name | Repository | Publisher | -| -------------- | ---------------------------------- | --------- | -| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | +| Name | Repository | Publisher | +|------|-------------|-----------| +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | + + diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index 20e33de7..c5e3325a 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -55,7 +55,9 @@ async function acquireLock(): Promise { while (fs.existsSync(lockFilePath)) { if (retryCount >= MAX_RETRIES) { - throw new Error("Failed to acquire lock: maximum retry attempts exceeded"); + throw new Error( + "Failed to acquire lock: maximum retry attempts exceeded", + ); } const backoffMs = BASE_DELAY_MS * Math.pow(2, retryCount); @@ -63,7 +65,9 @@ async function acquireLock(): Promise { const jitter = Math.random() * 0.3 * backoffMs; const delayMs = backoffMs + jitter; - logger.warn(`Lock file exists, waiting ${Math.round(delayMs)}ms before retry ${retryCount + 1}/${MAX_RETRIES}...`); + logger.warn( + `Lock file exists, waiting ${Math.round(delayMs)}ms before retry ${retryCount + 1}/${MAX_RETRIES}...`, + ); await sleep(delayMs); retryCount++; } From 3c348d34cdd5440a71c66b6f3dfce09e1ce1ad16 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 02:10:11 +0100 Subject: [PATCH 093/135] Fix: fixing the logger --- .gitignore | 1 + CREDITS.md | 73 +++++++++++++++++++++----------------------- src/utils/logger.ts | 74 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 98 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 6c617861..84449de1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ docker .test* # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### +*-audit.json # Logs logs *.log diff --git a/CREDITS.md b/CREDITS.md index be34b479..050b430b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -4,53 +4,48 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) ### License: (MIT AND CC-BY-3.0) -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| ----------------- | -------------------------------------------- | -------------------- | | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | - ### License: Apache-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | - +| Name | Repository | Publisher | +| ------------------------------------ | ------------------------------------------------------------- | --------------------- | +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | ### License: CC-BY-3.0 -| Name | Repository | Publisher | -|------|-------------|-----------| +| Name | Repository | Publisher | +| --------------------- | -------------------------------------------------- | -------------------- | | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | - ### License: Python-2.0 -| Name | Repository | Publisher | -|------|-------------|-----------| -| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | - - +| Name | Repository | Publisher | +| -------------- | ---------------------------------- | --------- | +| argparse@2.0.1 | https://github.com/nodeca/argparse | N/A | diff --git a/src/utils/logger.ts b/src/utils/logger.ts index e69955a9..8c1ea4a0 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,18 +1,70 @@ -import winston, { transport } from "winston"; +import { createLogger, format, transports } from "winston"; +import DailyRotateFile from "winston-daily-rotate-file"; import loggerConfig from "../config/loggerConfig"; -const transports: transport[] = [new winston.transports.Console()]; +// ANSI color codes for log level customization +const colors = { + gray: "\x1b[90m", + reset: "\x1b[0m", + white: "\x1b[97m", + red: "\x1b[31m", + green: "\x1b[32m", + yellow: "\x1b[33m", + blue: "\x1b[34m", +}; -transports.push( - new winston.transports.File({ - filename: "./logs/app.log", - }), -); +// Custom formatter to colorize log levels +function colorizeLogLevel(level: string, levelName: string) { + switch (level) { + case "info": + return `${colors.green}${levelName}${colors.reset}`; + case "debug": + return `${colors.blue}${levelName}${colors.reset}`; + case "error": + return `${colors.red}${levelName}${colors.reset}`; + case "warn": + return `${colors.yellow}${levelName}${colors.reset}`; + default: + return `${colors.gray}UNKNOWN${colors.reset}`; + } +} -const logger = winston.createLogger({ - level: loggerConfig.level, - format: loggerConfig.format, - transports, +// Filter out unwanted logs (example: Exit listeners logs) +const filterLogs = format((info) => { + if ( + typeof info.message === "string" && + info.message.includes("Exit listeners detected") + ) { + return false; + } + return info; +}); + +// Logger instance +const logger = createLogger({ + level: loggerConfig.level || "debug", + format: format.combine( + filterLogs(), + format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), + format.printf((info) => { + const level = info.level.toUpperCase().padEnd(5, " "); + const timestamp = `${colors.gray}${info.timestamp}${colors.reset}`; + const levelColorized = colorizeLogLevel(info.level.toLowerCase(), level); + const message = `${colors.white}${info.message}${colors.reset}`; + + return `${timestamp} ${levelColorized} : ${message}`; + }), + ), + transports: [ + new transports.Console(), + new DailyRotateFile({ + filename: "logs/app-%DATE%.log", + datePattern: "YYYY-MM-DD", + maxSize: "20m", + maxFiles: "14d", + zippedArchive: true, + }), + ], }); export default logger; From 59844643d243083ae10cdb7abcb8779a799ac400 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 18:29:56 +0100 Subject: [PATCH 094/135] Chore: Change routes to classes instead of huge functions --- TODO.md | 3 + src/controllers/auth.ts | 64 +++++++++++ src/controllers/containerController.ts | 32 +++--- src/handlers/api.ts | 138 ++++++++++++++++++++++++ src/handlers/auth.ts | 72 +++++++++++++ src/handlers/conf.ts | 97 +++++++++++++++++ src/handlers/data.ts | 93 ++++++++++++++++ src/handlers/frontend.ts | 138 ++++++++++++++++++++++++ src/handlers/ha.ts | 70 ++++++++++++ src/handlers/notification.ts | 73 +++++++++++++ src/handlers/response.ts | 41 +++++++ src/middleware/authMiddleware.ts | 13 ++- src/middleware/checkLock.ts | 10 +- src/routes/auth/routes.ts | 133 +++-------------------- src/routes/data/routes.ts | 89 ++------------- src/routes/frontendController/routes.ts | 115 ++++---------------- src/routes/getter/routes.ts | 125 +++------------------ src/routes/highavailability/routes.ts | 49 ++------- src/routes/notifications/routes.ts | 62 ++--------- src/routes/setter/routes.ts | 115 ++------------------ src/typings/dockerConfig.ts | 1 + src/typings/syncRequestBody.ts | 5 + src/typings/table.ts | 6 +- src/typings/template.ts | 5 + 24 files changed, 920 insertions(+), 629 deletions(-) create mode 100644 src/controllers/auth.ts create mode 100644 src/handlers/api.ts create mode 100644 src/handlers/auth.ts create mode 100644 src/handlers/conf.ts create mode 100644 src/handlers/data.ts create mode 100644 src/handlers/frontend.ts create mode 100644 src/handlers/ha.ts create mode 100644 src/handlers/notification.ts create mode 100644 src/handlers/response.ts create mode 100644 src/typings/syncRequestBody.ts create mode 100644 src/typings/template.ts diff --git a/TODO.md b/TODO.md index 36d32653..fc40ce6f 100644 --- a/TODO.md +++ b/TODO.md @@ -11,3 +11,6 @@ - [x] Better /api/status endpoint with connection status of each host - [x] Update notification service - [x] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) +- [ ] Better project structure +- [ ] Update logging => Better errors +- [ ] Update json responses and swagger diff --git a/src/controllers/auth.ts b/src/controllers/auth.ts new file mode 100644 index 00000000..905e39c9 --- /dev/null +++ b/src/controllers/auth.ts @@ -0,0 +1,64 @@ +import fs from "fs/promises"; +import logger from "../utils/logger"; +const passwordFile: string = "./src/data/password.json"; +const passwordBool: string = "./src/data/usePassword.txt"; + +async function authEnabled(): Promise { + let isAuthEnabled: boolean = false; + let data: string = ""; + try { + data = await fs.readFile(passwordBool, "utf8"); + isAuthEnabled = data.trim() === "true"; + return isAuthEnabled; + } catch (error: unknown) { + logger.error("Error reading file: ", error as Error); + return isAuthEnabled; + } +} + +async function readPasswordFile() { + let data: string = ""; + try { + data = await fs.readFile(passwordFile, "utf8"); + return data; + } catch (error: unknown) { + logger.error("Could not read saved password: ", error as Error); + return data; + } +} + +async function writePasswordFile(passwordData: string) { + try { + await fs.writeFile(passwordFile, passwordData); + setTrue(); + logger.debug("Authentication enabled"); + return "Authentication enabled"; + } catch (error: unknown) { + logger.error("Error writing password file:", error as Error); + return error; + } +} + +async function setTrue() { + try { + await fs.writeFile(passwordBool, "true", "utf8"); + logger.info(`Enabled authentication`); + return; + } catch (error: unknown) { + logger.error("Error writing to the file:", error as Error); + return; + } +} + +async function setFalse() { + try { + await fs.writeFile(passwordBool, "false", "utf8"); + logger.info(`Disabled authentication`); + return; + } catch (error: unknown) { + logger.error("Error writing to the file:", error as Error); + return; + } +} + +export { authEnabled, readPasswordFile, writePasswordFile, setFalse }; diff --git a/src/controllers/containerController.ts b/src/controllers/containerController.ts index ef1c8cef..61745e17 100644 --- a/src/controllers/containerController.ts +++ b/src/controllers/containerController.ts @@ -1,22 +1,25 @@ import getDockerClient from "../utils/dockerClient"; import logger from "../utils/logger"; import { Request, Response } from "express"; +import { createResponseHandler } from "../handlers/response"; const getContainers = async (req: Request, res: Response): Promise => { + const ResponseHandler = createResponseHandler(res); const host: string = (req.query.host as string) || "local"; + logger.info(`Fetching containers from host: ${host}`); + try { const docker = getDockerClient(host); const containers = await docker.listContainers(); - res.status(200).json(containers); - } catch (error: unknown) { - logger.error( - `Error fetching containers from host: ${host} - ${(error as Error).message || "Unknown error"} - Full error: ${JSON.stringify(error, null, 2)}`, + return ResponseHandler.rawData( + containers, + `Fetched containers from ${host}`, ); - res.status(500).json({ - error: `Error fetching containers: ${(error as Error).message || "Unknown error"}`, - }); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); } }; @@ -28,21 +31,20 @@ const getContainerStats = async ( logger.info( `Fetching stats for container: ${containerID} from host: ${containerHost}`, ); + const ResponseHandler = createResponseHandler(res); + try { const docker = getDockerClient(containerHost); const container = docker.getContainer(containerID); const stats = await container.stats({ stream: false }); - logger.info( + + return ResponseHandler.rawData( + stats, `Successfully fetched stats for container: ${containerID} from host: ${containerHost}`, ); - res.status(200).json(stats); } catch (error: unknown) { - logger.error( - `Error fetching stats for container: ${containerID} from host: ${containerHost} - ${(error as Error).message}`, - ); - res.status(500).json({ - error: `Error fetching container stats: ${(error as Error).message}`, - }); + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); } }; diff --git a/src/handlers/api.ts b/src/handlers/api.ts new file mode 100644 index 00000000..37529688 --- /dev/null +++ b/src/handlers/api.ts @@ -0,0 +1,138 @@ +import extractRelevantData from "../utils/extractHostData"; +import { Request, Response } from "express"; +import getDockerClient from "../utils/dockerClient"; +import fetchAllContainers from "../utils/containerService"; +import { getCurrentSchedule } from "../controllers/scheduler"; +import fs from "fs"; +import checkReachability from "../utils/connectionChecker"; +const configPath = "./src/data/dockerConfig.json"; +const userConf = "./src/data/user.conf"; +import { dockerConfig } from "../typings/dockerConfig"; +import { createResponseHandler } from "./response"; + +class ApiHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + hosts() { + const ResponseHandler = createResponseHandler(this.res); + try { + const rawData = fs.readFileSync(configPath, "utf-8"); + const config: dockerConfig = JSON.parse(rawData); + + if (!config.hosts) { + return ResponseHandler.error("No hosts defined in configuration.", 400); + } + + const hosts = config.hosts.map((host) => host.name); + return ResponseHandler.rawData(hosts, "Fetched data for all hosts"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + system() { + const ResponseHandler = createResponseHandler(this.res); + try { + const rawData = fs.readFileSync(userConf, "utf8"); + const config = JSON.parse(rawData); + + if (!config) { + return ResponseHandler.error("Received empty configuration", 400); + } + + return ResponseHandler.rawData(config, "Fetched system configuration"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async hostStats(hostName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + const docker = getDockerClient(hostName); + const info = await docker.info(); + const version = await docker.version(); + const relevantData = extractRelevantData({ hostName, info, version }); + + return ResponseHandler.rawData(relevantData, "Fetched Host stats"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async containers() { + const ResponseHandler = createResponseHandler(this.res); + try { + const allContainerData = await fetchAllContainers(); + return ResponseHandler.rawData( + allContainerData, + "Fetched all containers across all hosts", + ); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async config() { + const ResponseHandler = createResponseHandler(this.res); + try { + const rawData = fs.readFileSync(configPath); + const data = JSON.parse(rawData.toString()); + return ResponseHandler.rawData(data, "Fetched config"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + currentSchedule() { + const ResponseHandler = createResponseHandler(this.res); + try { + const currentSchedule = getCurrentSchedule(); + return ResponseHandler.rawData( + currentSchedule, + "Fetched current schedule", + ); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async status() { + const ResponseHandler = createResponseHandler(this.res); + try { + const data = await checkReachability(); + return ResponseHandler.rawData(data, "Fetched Status"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + frontendConfig() { + const configPath: string = "./src/data/frontendConfiguration.json"; + const ResponseHandler = createResponseHandler(this.res); + try { + const rawData = fs.readFileSync(configPath); + const data = JSON.parse(rawData.toString()); + ResponseHandler.rawData(data, "Fetched frontend configuration"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createApiHandler = (req: Request, res: Response) => + new ApiHandler(req, res); diff --git a/src/handlers/auth.ts b/src/handlers/auth.ts new file mode 100644 index 00000000..4dfbd3fb --- /dev/null +++ b/src/handlers/auth.ts @@ -0,0 +1,72 @@ +import { Request, Response } from "express"; +import { + authEnabled, + readPasswordFile, + writePasswordFile, + setFalse, +} from "../controllers/auth"; +import { createResponseHandler } from "./response"; +import bcrypt from "bcrypt"; + +const saltRounds: number = 10; + +class AuthenticationHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + async enable(password: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + if (await authEnabled()) { + return ResponseHandler.denied( + "Password Authentication is already enabled, please deactivate it first", + ); + } + + if (!password) { + return ResponseHandler.denied("Password is required"); + } + + const salt = await bcrypt.genSalt(saltRounds); + const hash = await bcrypt.hash(password, salt); + + const passwordData = { hash, salt }; + writePasswordFile(JSON.stringify(passwordData)); + + return ResponseHandler.ok("Authentication enabled successfully"); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async disable(password: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + if (!password) { + return ResponseHandler.denied("Password is required"); + } + + const storedData = JSON.parse(await readPasswordFile()); + const isPasswordValid = await bcrypt.compare(password, storedData.hash); + + if (!isPasswordValid) { + return ResponseHandler.error("Invalid password", 401); + } + + await setFalse(); + return ResponseHandler.ok("Authentication disabled successfully"); + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createAuthenticationHandler = (req: Request, res: Response) => + new AuthenticationHandler(req, res); diff --git a/src/handlers/conf.ts b/src/handlers/conf.ts new file mode 100644 index 00000000..e383c4d1 --- /dev/null +++ b/src/handlers/conf.ts @@ -0,0 +1,97 @@ +import { setFetchInterval, parseInterval } from "../controllers/scheduler"; +import { Request, Response } from "express"; +import fs from "fs"; +import { createResponseHandler } from "./response"; +import { target, dockerConfig } from "../typings/dockerConfig"; +const configPath: string = "./src/data/dockerConfig.json"; + +class ConfHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + addHost(req: Request) { + const ResponseHandler = createResponseHandler(this.res); + + try { + const { name, url, port } = req.query as unknown as target; + if (!name || !url || !port) { + return ResponseHandler.denied("Name, Port, and URL are required."); + } + + const config: dockerConfig = JSON.parse( + fs.readFileSync(configPath, "utf-8"), + ); + + if (config.hosts.some((host) => host.name === name)) { + return ResponseHandler.denied("Host already exists."); + } + + config.hosts.push({ name, url, port }); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + return ResponseHandler.ok("Host added successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + removeHost(req: Request) { + const ResponseHandler = createResponseHandler(this.res); + try { + const hostName = req.query.hostName as string; + + if (!hostName) { + return ResponseHandler.denied("Host name is required."); + } + + const currentState = fs.readFileSync(configPath, "utf-8"); + const config: dockerConfig = JSON.parse(currentState); + + const hostIndex = config.hosts.findIndex( + (host) => host.name === hostName, + ); + + if (hostIndex === -1) { + return ResponseHandler.error("Host not found.", 404); + } + + config.hosts.splice(hostIndex, 1); + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + return ResponseHandler.ok("Host removed successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + scheduler(req: Request) { + const ResponseHandler = createResponseHandler(this.res); + try { + const interval = req.query.interval as string; + const newInterval = parseInterval(interval); + + if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { + return ResponseHandler.denied( + "Interval must be between 5 minutes and 6 hours.", + ); + } + + setFetchInterval(newInterval); + return ResponseHandler.ok("Updated interval"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createConfHandler = (req: Request, res: Response) => + new ConfHandler(req, res); diff --git a/src/handlers/data.ts b/src/handlers/data.ts new file mode 100644 index 00000000..fd3515d6 --- /dev/null +++ b/src/handlers/data.ts @@ -0,0 +1,93 @@ +import { Response, Request } from "express"; +import db from "../config/db"; +import { Table, DataRow } from "../typings/table"; +import { createResponseHandler } from "./response"; + +function formatRows(rows: DataRow[]): Record { + return rows.reduce( + ( + acc: Record, + row, + index: number, + ): Record => { + acc[index] = JSON.parse(row.info); + return acc; + }, + {}, + ); +} + +class DatabaseHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + latest() { + const ResponseHandler = createResponseHandler(this.res); + db.get( + "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", + (error: unknown, row: Partial> | undefined) => { + if (error) { + return ResponseHandler.critical(error as string); + } + + if (!row || !row.info) { + return ResponseHandler.error( + "No data available for /data/latest", + 404, + ); + } + + try { + return ResponseHandler.rawData( + JSON.parse(row.info), + "Read latest data", + ); + } catch (error: unknown) { + const errorMsg = + error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + }, + ); + } + + all() { + const ResponseHandler = createResponseHandler(this.res); + const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); + + db.all( + "SELECT info FROM data WHERE timestamp >= ?", + [oneDayAgo], + (error: unknown, rows: Pick[] | undefined) => { + if (error) { + return ResponseHandler.critical(error as string); + } + + if (!rows || rows.length === 0) { + return ResponseHandler.error("No data available", 404); + } + + return ResponseHandler.rawData(formatRows(rows), "Read database"); + }, + ); + } + + clear() { + const ResponseHandler = createResponseHandler(this.res); + db.run("DELETE FROM data", (error: unknown) => { + if (error) { + return ResponseHandler.critical(error as string); + } + + return ResponseHandler.ok("Database cleared successfully"); + }); + } +} + +export const createDatabaseHandler = (req: Request, res: Response) => + new DatabaseHandler(req, res); diff --git a/src/handlers/frontend.ts b/src/handlers/frontend.ts new file mode 100644 index 00000000..6b2edc55 --- /dev/null +++ b/src/handlers/frontend.ts @@ -0,0 +1,138 @@ +import { Request, Response } from "express"; +import { createResponseHandler } from "./response"; +import { + hideContainer, + unhideContainer, + addTagToContainer, + removeTagFromContainer, + pinContainer, + unpinContainer, + setLink, + removeLink, + setIcon, + removeIcon, +} from "../controllers/frontendConfiguration"; + +class FrontendHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + async show(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await unhideContainer(containerName); + return ResponseHandler.ok("Container unhidden successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async hide(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await hideContainer(containerName); + return ResponseHandler.ok("Hid container succesfully"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async addTag(containerName: string, tag: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await addTagToContainer(containerName, tag); + return ResponseHandler.ok("Tag added successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async removeTag(containerName: string, tag: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await removeTagFromContainer(containerName, tag); + ResponseHandler.ok("Tag removed successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async pin(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await pinContainer(containerName); + return ResponseHandler.ok("Container pinned successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async unPin(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await unpinContainer(containerName); + return ResponseHandler.ok("Container unpinned successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async addLink(containerName: string, link: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await setLink(containerName, link); + return ResponseHandler.ok("Link added successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async removeLink(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await removeLink(containerName); + return ResponseHandler.ok("Removed link succesfully"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async addIcon(containerName: string, icon: string, useCustomIcon: string) { + const ResponseHandler = createResponseHandler(this.res); + const iconBool = useCustomIcon === "true"; + try { + await setIcon(containerName, icon, iconBool); + return ResponseHandler.ok("Icon added successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async removeIcon(containerName: string) { + const ResponseHandler = createResponseHandler(this.res); + try { + await removeIcon(containerName); + return ResponseHandler.ok("Icon removed successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createFrontendHandler = (req: Request, res: Response) => + new FrontendHandler(req, res); diff --git a/src/handlers/ha.ts b/src/handlers/ha.ts new file mode 100644 index 00000000..16c9ae19 --- /dev/null +++ b/src/handlers/ha.ts @@ -0,0 +1,70 @@ +import { Request, Response } from "express"; +import logger from "../utils/logger"; +import { + readConfig, + prepareFilesForSync, + ensureFileExists, +} from "../controllers/highAvailability"; +import { createResponseHandler } from "./response"; + +class HaHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + async config() { + const ResponseHandler = createResponseHandler(this.res); + try { + const data = await readConfig(); + return ResponseHandler.rawData(data, "Fetched HA-Config"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async sync(req: Request) { + const ResponseHandler = createResponseHandler(this.res); + try { + const { files } = req.body; + logger.info("Received synchronization request from master node."); + if (!files || typeof files !== "object") { + return ResponseHandler.error( + "Invalid request: 'files' object is missing or invalid.", + 400, + ); + } + + for (const [filePath, content] of Object.entries(files)) { + await ensureFileExists(filePath, content as string); + } + + return ResponseHandler.ok("Synchronization completed successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async prepare() { + const ResponseHandler = createResponseHandler(this.res); + try { + logger.info("Preparing files for synchronization."); + const fileData = await prepareFilesForSync(); + return ResponseHandler.rawData( + fileData, + "Done preparing files for synchronization", + ); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createHaHandler = (req: Request, res: Response) => + new HaHandler(req, res); diff --git a/src/handlers/notification.ts b/src/handlers/notification.ts new file mode 100644 index 00000000..ad5c2938 --- /dev/null +++ b/src/handlers/notification.ts @@ -0,0 +1,73 @@ +import { Request, Response } from "express"; +import fs from "fs"; +import notify from "../utils/notifications/_notify"; +const dataTemplate = "./src/data/template.json"; +import { TemplateData } from "../typings/template"; +import { createResponseHandler } from "./response"; + +function isTemplateData(data: TemplateData): data is TemplateData { + return ( + data !== null && typeof data === "object" && typeof data.text === "string" + ); +} + +class NotificationHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + getTemplate() { + const ResponseHandler = createResponseHandler(this.res); + try { + fs.readFile(dataTemplate, "utf-8", (error: unknown, data) => { + if (error) { + return ResponseHandler.error(error as string, 400); + } + return ResponseHandler.rawData(data, "Fetched notification template"); + }); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + setTemplate(req: Request) { + const ResponseHandler = createResponseHandler(this.res); + const newTemplate: TemplateData = req.body; + + try { + if (!isTemplateData(newTemplate)) { + return ResponseHandler.error( + "Invalid input format. Expected JSON with a 'text' field.", + 400, + ); + } + + fs.writeFileSync(dataTemplate, JSON.stringify(newTemplate, null, 2)); + return ResponseHandler.ok("Template updated successfully."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async test(req: Request) { + const { type, containerId } = req.params; + const ResponseHandler = createResponseHandler(this.res); + + try { + await notify(type, containerId); + return ResponseHandler.ok("Sent test notification"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } +} + +export const createNotificationHandler = (req: Request, res: Response) => + new NotificationHandler(req, res); diff --git a/src/handlers/response.ts b/src/handlers/response.ts new file mode 100644 index 00000000..8c6e95b8 --- /dev/null +++ b/src/handlers/response.ts @@ -0,0 +1,41 @@ +import { Response } from "express"; +import logger from "../utils/logger"; + +class ResponseHandler { + private res: Response; + + constructor(res: Response) { + this.res = res; + } + + rawData(data: unknown, message: string) { + logger.info(message); + this.res.status(200).json(data); + } + + ok(message: string) { + logger.info(message); + this.res.status(200).json({ status: "success", message }); + } + + denied(message: string) { + logger.warn(message); + this.res.status(403).json({ status: "denied", message }); + } + + error(message: string, code: number) { + logger.error(`Code: ${code} - ${message}`); + this.res.status(code).json({ status: "error", message }); + } + + critical(log: string) { + logger.error(log); + this.res.status(500).json({ + status: "critical", + message: "Please see the server logs for more info", + }); + } +} + +export const createResponseHandler = (res: Response) => + new ResponseHandler(res); diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 500a7fa8..4afb3939 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import { Request, Response, NextFunction } from "express"; import logger from "../utils/logger"; import { rateLimitedReadFile } from "../utils/rateLimitFS"; - +import { createResponseHandler } from "../handlers/response"; const passwordFile = "./src/data/password.json"; const passwordBool = "./src/data/usePassword.txt"; @@ -11,6 +11,7 @@ async function authMiddleware( res: Response, next: NextFunction, ): Promise { + const ResponseHandler = createResponseHandler(res); try { const authStatusData = await rateLimitedReadFile(passwordBool); const isAuthEnabled = authStatusData.trim() === "true"; @@ -23,8 +24,7 @@ async function authMiddleware( const providedPassword = req.headers["x-password"]; if (!providedPassword) { - logger.error("Password required - Denied"); - res.status(401).json({ message: "Password required" }); + ResponseHandler.denied("Password required"); return; } @@ -36,16 +36,15 @@ async function authMiddleware( storedData.hash, ); if (!passwordMatch) { - logger.error("Invalid Password - Denied access"); - res.status(401).json({ message: "Invalid password" }); + ResponseHandler.denied("Invalid Password"); return; } logger.debug("Authentication succesfull"); next(); } catch (error: unknown) { - logger.error("Error in authMiddleware:", error); - res.status(500).json({ message: "Internal server error" }); + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); } } diff --git a/src/middleware/checkLock.ts b/src/middleware/checkLock.ts index 73740a07..c01540fe 100644 --- a/src/middleware/checkLock.ts +++ b/src/middleware/checkLock.ts @@ -1,5 +1,6 @@ import { Request, Response, NextFunction } from "express"; import { rateLimitedExistsSync } from "../utils/rateLimitFS"; +import { createResponseHandler } from "../handlers/response"; const lockFilePath = "./src/data/ha.lock"; @@ -8,11 +9,12 @@ export async function blockWhileLocked( res: Response, next: NextFunction, ): Promise { + const ResponseHandler = createResponseHandler(res); if (await rateLimitedExistsSync(lockFilePath)) { - res.status(503).json({ - error: - "Service unavailable. The high-availability lock is currently active. Please try again later.", - }); + ResponseHandler.error( + "Service unavailable. The high-availability lock is currently active. Please try again later.", + 503, + ); } else { next(); } diff --git a/src/routes/auth/routes.ts b/src/routes/auth/routes.ts index f7e0b18e..47ff6f25 100644 --- a/src/routes/auth/routes.ts +++ b/src/routes/auth/routes.ts @@ -1,69 +1,7 @@ import { Router, Request, Response } from "express"; -import bcrypt from "bcrypt"; -import fs from "fs/promises"; -import logger from "../../utils/logger"; -const passwordFile: string = "./src/data/password.json"; -const passwordBool: string = "./src/data/usePassword.txt"; -const saltRounds: number = 10; -const router: Router = Router(); +import { createAuthenticationHandler } from "../../handlers/auth"; -async function authEnabled(): Promise { - let isAuthEnabled: boolean = false; - let data: string = ""; - try { - data = await fs.readFile(passwordBool, "utf8"); - isAuthEnabled = data.trim() === "true"; - return isAuthEnabled; - } catch (error: unknown) { - logger.error("Error reading file: ", error as Error); - return isAuthEnabled; - } -} - -async function readPasswordFile() { - let data: string = ""; - try { - data = await fs.readFile(passwordFile, "utf8"); - return data; - } catch (error: unknown) { - logger.error("Could not read saved password: ", error as Error); - return data; - } -} - -async function writePasswordFile(passwordData: string) { - try { - await fs.writeFile(passwordFile, passwordData); - setTrue(); - logger.debug("Authentication enabled"); - return "Authentication enabled"; - } catch (error: unknown) { - logger.error("Error writing password file:", error as Error); - return error; - } -} - -async function setTrue() { - try { - await fs.writeFile(passwordBool, "true", "utf8"); - logger.info(`Enabled authentication`); - return; - } catch (error: unknown) { - logger.error("Error writing to the file:", error as Error); - return; - } -} - -async function setFalse() { - try { - await fs.writeFile(passwordBool, "false", "utf8"); - logger.info(`Disabled authentication`); - return; - } catch (error: unknown) { - logger.error("Error writing to the file:", error as Error); - return; - } -} +const router = Router(); /** * @swagger @@ -75,6 +13,8 @@ async function setFalse() { * - name: password * in: query * required: true + * schema: + * type: string * responses: * 200: * description: Authentication enabled. @@ -84,39 +24,9 @@ async function setFalse() { * description: Error saving password. */ router.post("/enable", async (req: Request, res: Response): Promise => { - try { - const password = req.query.password as string; - - if (await authEnabled()) { - logger.error( - "Password Authentication is already enabled, please deactivate it first", - ); - res.status(401).json({ - message: - "Password Authentication is already enabled, please deactivate it first", - }); - return; - } - - if (!password) { - logger.error("Password is required"); - res.status(400).json({ message: "Password is required" }); - return; - } - - const salt = await bcrypt.genSalt(saltRounds); - const hash = await bcrypt.hash(password, salt); - - const passwordData = { hash, salt }; - writePasswordFile(JSON.stringify(passwordData)); - - res - .status(200) - .json({ message: "Password Authentication enabled successfully" }); - } catch (error: unknown) { - logger.error(`Error enabling password authentication: ${error as Error}`); - res.status(500).json({ message: "An error occurred" }); - } + const password = req.query.password as string; + const handler = createAuthenticationHandler(req, res); + await handler.enable(password); }); /** @@ -129,6 +39,8 @@ router.post("/enable", async (req: Request, res: Response): Promise => { * - name: password * in: query * required: true + * schema: + * type: string * responses: * 200: * description: Authentication disabled. @@ -140,30 +52,9 @@ router.post("/enable", async (req: Request, res: Response): Promise => { * description: Error disabling authentication. */ router.post("/disable", async (req: Request, res: Response): Promise => { - try { - const password = req.query.password as string; - - if (!password) { - logger.error("Password is required!"); - res.status(400).json({ message: "Password is required" }); - return; - } - - const storedData = JSON.parse(await readPasswordFile()); - - const isPasswordValid = await bcrypt.compare(password, storedData.hash); - if (!isPasswordValid) { - logger.error("Invalid password"); - res.status(401).json({ message: "Invalid password" }); - return; - } - - await setFalse(); // Assuming this is an async function - res.status(200).json({ message: "Authentication disabled" }); - } catch (error: unknown) { - logger.error(`Error disabling authentication: ${error as Error}`); - res.status(500).json({ message: "An error occurred" }); - } + const password = req.query.password as string; + const handler = createAuthenticationHandler(req, res); + await handler.disable(password); }); export default router; diff --git a/src/routes/data/routes.ts b/src/routes/data/routes.ts index 108fafe4..92a7f976 100644 --- a/src/routes/data/routes.ts +++ b/src/routes/data/routes.ts @@ -1,26 +1,6 @@ -import express from "express"; +import express, { Request, Response } from "express"; const router = express.Router(); -import db from "../../config/db"; -import logger from "../../utils/logger"; -import Table from "../../typings/table"; - -interface DataRow { - info: string; -} - -function formatRows(rows: DataRow[]): Record { - return rows.reduce( - ( - acc: Record, - row, - index: number, - ): Record => { - acc[index] = JSON.parse(row.info); - return acc; - }, - {}, - ); -} +import { createDatabaseHandler } from "../../handlers/data"; /** * @swagger @@ -90,29 +70,9 @@ function formatRows(rows: DataRow[]): Record { * description: Networking mode for the container * example: "bridge" */ -router.get("/latest", (req, res) => { - db.get( - "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", - (error: unknown, row: Partial> | undefined) => { - if (error) { - logger.error("Error fetching latest data:", (error as Error).message); - return res.status(500).json({ error: "Internal server error" }); - } - - if (!row || !row.info) { - logger.warn("No data available for /data/latest"); - return res.status(404).json({ error: "No data available" }); - } - - logger.debug("Fetching /data/latest"); - try { - res.json(JSON.parse(row.info)); - } catch (error: unknown) { - logger.error("Error parsing data:", (error as Error).message); - res.status(500).json({ error: "Data format error" }); - } - }, - ); +router.get("/latest", (req: Request, res: Response) => { + const DatabaseHandler = createDatabaseHandler(req, res); + return DatabaseHandler.latest(); }); /** @@ -162,30 +122,9 @@ router.get("/latest", (req, res) => { * type: number * example: 3072 */ -router.get("/all", (req, res) => { - const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); - - db.all( - "SELECT info FROM data WHERE timestamp >= ?", - [oneDayAgo], - (error: unknown, rows: Pick[] | undefined) => { - if (error) { - logger.error( - "Error fetching data from last 24 hours:", - (error as Error).message, - ); - return res.status(500).json({ error: "Internal server error" }); - } - - logger.debug("Fetching /data/time/24h"); - if (!rows || rows.length === 0) { - logger.warn("No data available for /data/time/24h"); - return res.status(404).json({ error: "No data available" }); - } - - res.json(formatRows(rows)); - }, - ); +router.get("/all", (req: Request, res: Response) => { + const DatabaseHandler = createDatabaseHandler(req, res); + return DatabaseHandler.all(); }); /** @@ -207,15 +146,9 @@ router.get("/all", (req, res) => { * description: Success message upon database clearance * example: "Database cleared successfully." */ -router.delete("/clear", (req, res) => { - db.run("DELETE FROM data", (error: unknown) => { - if (error) { - logger.error("Error clearing the database:", (error as Error).message); - return res.status(500).json({ error: "Internal server error" }); - } - logger.debug("Database cleared successfully"); - res.json({ message: "Database cleared successfully" }); - }); +router.delete("/clear", (req: Request, res: Response) => { + const DatabaseHandler = createDatabaseHandler(req, res); + return DatabaseHandler.clear(); }); export default router; diff --git a/src/routes/frontendController/routes.ts b/src/routes/frontendController/routes.ts index 0de95fe9..39500c51 100644 --- a/src/routes/frontendController/routes.ts +++ b/src/routes/frontendController/routes.ts @@ -1,25 +1,6 @@ import express from "express"; const router = express.Router(); -import { - hideContainer, - unhideContainer, - addTagToContainer, - removeTagFromContainer, - pinContainer, - unpinContainer, - setLink, - removeLink, - setIcon, - removeIcon, -} from "../../controllers/frontendConfiguration"; - -/* -____ ___ ____ _____ -| _ \ / _ \/ ___|_ _| -| |_) | | | \___ \ | | -| __/| |_| |___) || | -|_| \___/|____/ |_| -*/ +import { createFrontendHandler } from "../../handlers/frontend"; /** * @swagger @@ -62,15 +43,10 @@ ____ ___ ____ _____ * type: string * description: Error message */ -// Unhide a container router.post("/show/:containerName", async (req, res) => { - const { containerName } = req.params; - try { - await unhideContainer(containerName); - res.status(200).json({ message: "Container unhidden successfully." }); - } catch (error: unknown) { - res.status(500).json({ error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + const containerName = req.params.containerName; + return FrontendHandler.show(containerName); }); /** @@ -120,15 +96,10 @@ router.post("/show/:containerName", async (req, res) => { * type: string * description: Error message */ -// Add a tag to a container router.post("/tag/:containerName/:tag", async (req, res) => { const { containerName, tag } = req.params; - try { - await addTagToContainer(containerName, tag); - res.json({ success: true, message: "Tag added successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.addTag(containerName, tag); }); /** @@ -172,15 +143,10 @@ router.post("/tag/:containerName/:tag", async (req, res) => { * type: string * description: Error message */ -// Pin a container router.post("/pin/:containerName", async (req, res) => { const { containerName } = req.params; - try { - await pinContainer(containerName); - res.json({ success: true, message: "Container pinned successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.pin(containerName); }); /** @@ -230,15 +196,10 @@ router.post("/pin/:containerName", async (req, res) => { * type: string * description: Error message */ -// Add link to container router.post("/add-link/:containerName/:link", async (req, res) => { const { containerName, link } = req.params; - try { - await setLink(containerName, link); - res.json({ success: true, message: "Link added successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.addLink(containerName, link); }); /** @@ -294,19 +255,12 @@ router.post("/add-link/:containerName/:link", async (req, res) => { * type: string * description: Error message */ -// Add Icon to container router.post( "/add-icon/:containerName/:icon/:useCustomIcon", async (req, res) => { const { containerName, icon, useCustomIcon } = req.params; - try { - const custom = useCustomIcon === "true"; - - await setIcon(containerName, icon, custom); - res.json({ success: true, message: "Icon added successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.addIcon(containerName, icon, useCustomIcon); }, ); @@ -362,13 +316,8 @@ router.post( // Hide a container router.delete("/hide/:containerName", async (req, res) => { const { containerName } = req.params; - const target = containerName; - try { - await hideContainer(target); - res.json({ success: true, message: `Container, ${target}, hidden.` }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.hide(containerName); }); /** @@ -418,15 +367,10 @@ router.delete("/hide/:containerName", async (req, res) => { * type: string * description: Error message */ -// Remove a tag from a container router.delete("/remove-tag/:containerName/:tag", async (req, res) => { const { containerName, tag } = req.params; - try { - await removeTagFromContainer(containerName, tag); - res.json({ success: true, message: "Tag removed successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.removeTag(containerName, tag); }); /** @@ -470,15 +414,10 @@ router.delete("/remove-tag/:containerName/:tag", async (req, res) => { * type: string * description: Error message */ -// Unpin a container router.delete("/unpin/:containerName", async (req, res) => { const { containerName } = req.params; - try { - await unpinContainer(containerName); - res.json({ success: true, message: "Container unpinned successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.unPin(containerName); }); /** @@ -522,15 +461,10 @@ router.delete("/unpin/:containerName", async (req, res) => { * type: string * description: Error message */ -// Remove link from container router.delete("/remove-link/:containerName", async (req, res) => { const { containerName } = req.params; - try { - await removeLink(containerName); - res.json({ success: true, message: "Link removed successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.removeLink(containerName); }); /** @@ -574,15 +508,10 @@ router.delete("/remove-link/:containerName", async (req, res) => { * type: string * description: Error message */ -// Remove icon from container router.delete("/remove-icon/:containerName", async (req, res) => { const { containerName } = req.params; - try { - await removeIcon(containerName); - res.json({ success: true, message: "Icon removed successfully." }); - } catch (error: unknown) { - res.status(500).json({ success: false, error: (error as Error).message }); - } + const FrontendHandler = createFrontendHandler(req, res); + return FrontendHandler.removeIcon(containerName); }); export default router; diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index d278075c..0912d48b 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -1,15 +1,6 @@ -import extractRelevantData from "../../utils/extractHostData"; import { Router, Request, Response } from "express"; -import getDockerClient from "../../utils/dockerClient"; -import fetchAllContainers from "../../utils/containerService"; -import { getCurrentSchedule } from "../../controllers/scheduler"; -import logger from "../../utils/logger"; -import fs from "fs"; -import checkReachability from "../../utils/connectionChecker"; -const configPath = "./src/data/dockerConfig.json"; +import { createApiHandler } from "../../handlers/api"; const router = Router(); -const userConf = "./src/data/user.conf"; -import { dockerConfig } from "../../typings/dockerConfig"; /** * @swagger @@ -32,22 +23,8 @@ import { dockerConfig } from "../../typings/dockerConfig"; * example: ["local", "remote1"] */ router.get("/hosts", (req: Request, res: Response) => { - logger.info(`Fetching config: ${configPath}`); - try { - const rawData = fs.readFileSync(configPath, "utf-8"); - const config: dockerConfig = JSON.parse(rawData); - - if (!config.hosts) { - throw new Error("No hosts defined in configuration."); - } - - const hosts = config.hosts.map((host) => host.name); - logger.debug("Fetching all available Docker hosts"); - res.status(200).json({ hosts }); - } catch (error: unknown) { - logger.error("Error fetching hosts: " + (error as Error).message); - res.status(500).json({ error: "Failed to fetch Docker hosts" }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.hosts(); }); /** @@ -76,20 +53,8 @@ router.get("/hosts", (req: Request, res: Response) => { * description: Error message detailing the issue encountered. */ router.get("/system", (req: Request, res: Response) => { - logger.info(`Fetching ${userConf}`); - - try { - const rawData = fs.readFileSync(userConf, "utf8"); - const config = JSON.parse(rawData); - - if (!config) { - res.status(500).json({ error: `Error received empty ${userConf}` }); - } - res.status(200).json(config); - } catch (error: unknown) { - logger.error(`Could not fetch ${userConf}: ${error as Error}`); - res.status(500).json({ error: `Failed to fetch ${userConf}` }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.system(); }); /** @@ -135,22 +100,8 @@ router.get("/system", (req: Request, res: Response) => { */ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { const { hostName } = req.params; - logger.info(`Fetching stats for host: ${hostName}`); - try { - const docker = getDockerClient(hostName); - const info = await docker.info(); - const version = await docker.version(); - const relevantData = extractRelevantData({ hostName, info, version }); - - res.status(200).json(relevantData); - } catch (error: unknown) { - logger.error( - `Error fetching stats for host: ${hostName} - ${(error as Error).message || "Unknown error"}`, - ); - res.status(500).json({ - error: `Error fetching host stats: ${(error as Error).message || "Unknown error"}`, - }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.hostStats(hostName); }); /** @@ -227,15 +178,8 @@ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { * description: Error message detailing the issue encountered. */ router.get("/containers", async (req: Request, res: Response) => { - logger.info("Fetching all containers across all hosts"); - try { - const allContainerData = await fetchAllContainers(); - logger.debug("Fetched /api/containers"); - res.status(200).json(allContainerData); - } catch (error: unknown) { - logger.error(`Error fetching containers: ${(error as Error).message}`); - res.status(500).json({ error: "Failed to fetch containers" }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.containers(); }); /** @@ -264,17 +208,8 @@ router.get("/containers", async (req: Request, res: Response) => { * description: Error message detailing the issue encountered. */ router.get("/config", async (req: Request, res: Response) => { - try { - const rawData = fs.readFileSync(configPath); - const jsonData = JSON.parse(rawData.toString()); - logger.debug("Fetching /api/config"); - res.status(200).json(jsonData); - } catch (error: unknown) { - logger.error( - "Error loading dockerConfig.json: " + (error as Error).message, - ); - res.status(500).json({ error: "Failed to load Docker configuration" }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.config(); }); /** @@ -296,9 +231,8 @@ router.get("/config", async (req: Request, res: Response) => { * description: Current fetch interval in seconds. */ router.get("/current-schedule", (req: Request, res: Response) => { - const currentSchedule = getCurrentSchedule(); - logger.debug("Fetching current shedule"); - res.json(currentSchedule); + const ApiHandler = createApiHandler(req, res); + return ApiHandler.currentSchedule(); }); /** @@ -331,13 +265,8 @@ router.get("/current-schedule", (req: Request, res: Response) => { */ router.get("/status", async (req: Request, res: Response) => { - logger.debug("Fetching /api/status"); - try { - const jsonData = await checkReachability(); - res.status(200).json(jsonData); - } catch (error: unknown) { - logger.error(`Error while fetching data: ${error as Error}`); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.status(); }); /** @@ -383,28 +312,8 @@ router.get("/status", async (req: Request, res: Response) => { * description: Error message */ router.get("/frontend-config", (req: Request, res: Response) => { - const configPath: string = "./src/data/frontendConfiguration.json"; - - fs.stat(configPath, (exists) => { - if (exists == null) { - logger.debug(`${configPath} exists, trying to read it`); - } else if (exists.code === "ENOENT") { - logger.warn(`${configPath} doesn't exist, trying to create it`); - fs.promises.writeFile(configPath, JSON.stringify([], null, 2), "utf-8"); - } - }); - - try { - const rawData = fs.readFileSync(configPath); - const jsonData = JSON.parse(rawData.toString()); - - res.status(200).json(jsonData); - } catch (error: unknown) { - logger.error( - "Error loading frontendConfiguration.json: " + (error as Error).message, - ); - res.status(500).json({ error: "Failed to load Frontend configuration" }); - } + const ApiHandler = createApiHandler(req, res); + return ApiHandler.frontendConfig(); }); export default router; diff --git a/src/routes/highavailability/routes.ts b/src/routes/highavailability/routes.ts index 3fadb02e..86057bcd 100644 --- a/src/routes/highavailability/routes.ts +++ b/src/routes/highavailability/routes.ts @@ -1,16 +1,6 @@ -// File: /src/routes/ha/routes.ts import { Router, Request, Response } from "express"; -import logger from "../../utils/logger"; -import { - readConfig, - prepareFilesForSync, - ensureFileExists, -} from "../../controllers/highAvailability"; - -interface SyncRequestBody { - files: Record; -} - +import { SyncRequestBody } from "../../typings/syncRequestBody"; +import { createHaHandler } from "../../handlers/ha"; const router = Router(); /** @@ -24,9 +14,8 @@ const router = Router(); * description: A JSON object containing the config. */ router.get("/config", async (req: Request, res: Response) => { - logger.info("Getting the HA-Config"); - const data = await readConfig(); - res.status(200).json(data); + const HaHandler = createHaHandler(req, res); + return HaHandler.config(); }); /** @@ -45,29 +34,8 @@ router.post( req: Request<{}, {}, SyncRequestBody>, // eslint-disable-line res: Response, ): Promise => { - try { - const { files } = req.body; - - if (!files || typeof files !== "object") { - const errorMsg = - "Invalid request: 'files' object is missing or invalid."; - logger.error(errorMsg); - res.status(400).json({ message: errorMsg }); - return; - } - - logger.info("Received synchronization request from master node."); - - for (const [filePath, content] of Object.entries(files)) { - await ensureFileExists(filePath, content); - } - - logger.info("Synchronization completed successfully."); - res.status(200).json({ message: "Synchronization completed." }); - } catch (error) { - logger.error(`Error during synchronization: ${(error as Error).message}`); - res.status(500).json({ message: "Synchronization failed." }); - } + const HaHandler = createHaHandler(req, res); + return HaHandler.sync(req); }, ); @@ -82,9 +50,8 @@ router.post( * description: A JSON object containing files to sync. */ router.get("/prepare-sync", async (req: Request, res: Response) => { - logger.info("Preparing files for synchronization."); - const fileData = await prepareFilesForSync(); - res.status(200).json(fileData); + const HaHandler = createHaHandler(req, res); + return HaHandler.prepare(); }); export default router; diff --git a/src/routes/notifications/routes.ts b/src/routes/notifications/routes.ts index 17cf6986..4544b8ce 100644 --- a/src/routes/notifications/routes.ts +++ b/src/routes/notifications/routes.ts @@ -1,26 +1,7 @@ import { Request, Response, Router } from "express"; -import logger from "../../utils/logger"; -import fs from "fs"; -import notify from "../../utils/notifications/_notify"; -const dataTemplate = "./src/data/template.json"; +import { createNotificationHandler } from "../../handlers/notification"; const router = Router(); -/////////// -// Will be moved! - -interface TemplateData { - text: string; -} - -function isTemplateData(data: TemplateData): data is TemplateData { - return ( - data !== null && typeof data === "object" && typeof data.text === "string" - ); -} - -// Will be moved -/////////// - /** * @swagger * /notification-service/get-template: @@ -53,13 +34,8 @@ function isTemplateData(data: TemplateData): data is TemplateData { * description: Error message */ router.get("/get-template", (req: Request, res: Response) => { - fs.readFile(dataTemplate, "utf-8", (error, data) => { - if (error) { - logger.error("Errored opening:", error); - return res.status(500).json({ message: `Error opening: ${error}` }); - } - res.json(JSON.parse(data)); - }); + const NotificationHandler = createNotificationHandler(req, res); + return NotificationHandler.getTemplate(); }); /** @@ -98,27 +74,8 @@ router.get("/get-template", (req: Request, res: Response) => { * description: Error message */ router.post("/set-template", (req: Request, res: Response): void => { - const newData: TemplateData = req.body; - - if (!isTemplateData(newData)) { - res.status(400).json({ - message: "Invalid input format. Expected JSON with a 'text' field.", - }); - return; - } - - fs.promises - .writeFile(dataTemplate, JSON.stringify(newData, null, 2), "utf-8") - .then(() => { - logger.info("Template updated successfully."); - res.json({ message: "Template updated successfully." }); - }) - .catch((error) => { - logger.error("Error writing to file: " + error.message); - res - .status(500) - .json({ message: `Error writing to file: ${error.message}` }); - }); + const NotificationHandler = createNotificationHandler(req, res); + return NotificationHandler.setTemplate(req); }); /** @@ -165,13 +122,8 @@ router.post("/set-template", (req: Request, res: Response): void => { * type: string */ router.post("/test/:type/:containerId", async (req: Request, res: Response) => { - const { type, containerId } = req.params; - try { - await notify(type, containerId); - res.json({ success: true, message: `Sent test notification to ${type}` }); - } catch (error: unknown) { - res.json({ success: false, message: `Errored: ${error as Error}` }); - } + const NotificationHandler = createNotificationHandler(req, res); + NotificationHandler.test(req); }); export default router; diff --git a/src/routes/setter/routes.ts b/src/routes/setter/routes.ts index 96915a92..75ef747d 100644 --- a/src/routes/setter/routes.ts +++ b/src/routes/setter/routes.ts @@ -1,20 +1,6 @@ -import { setFetchInterval, parseInterval } from "../../controllers/scheduler"; -import logger from "../../utils/logger"; import express, { Router, Request, Response } from "express"; -import fs from "fs"; - +import { createConfHandler } from "../../handlers/conf"; const router: Router = express.Router(); -const configPath: string = "./src/data/dockerConfig.json"; - -interface Host { - name: string; - url: string; - port: string; -} - -interface DockerConfig { - hosts: Host[]; -} /** * @swagger @@ -43,46 +29,10 @@ interface DockerConfig { * 500: * description: An error occurred while adding the host. */ - -router.put( - "/addHost", - async ( - req: Request< - unknown, - unknown, - unknown, - { name: string; url: string; port: string } - >, - res: Response, - ): Promise => { - const { name, url, port } = req.query; - - if (!name || !url || !port) { - res.status(400).json({ error: "Name, Port, and URL are required." }); - return; - } - - try { - const config: DockerConfig = JSON.parse( - fs.readFileSync(configPath, "utf-8"), - ); - - if (config.hosts.some((host) => host.name === name)) { - res.status(400).json({ error: "Host already exists." }); - return; - } - - config.hosts.push({ name, url, port }); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - logger.info(`Added new host: ${name}`); - res.status(200).json({ message: "Host added successfully." }); - } catch (error: unknown) { - const err = error as Error; - logger.error("Error adding host: " + err.message); - res.status(500).json({ error: "Failed to add host." }); - } - }, -); +router.put("/addHost", async (req: Request, res: Response): Promise => { + const ConfHandler = createConfHandler(req, res); + return ConfHandler.addHost(req); +}); /** * @swagger @@ -102,24 +52,8 @@ router.put( * description: Invalid interval format or out of range. */ router.put("/scheduler", (req: Request, res: Response) => { - const interval = req.query.interval as string; - - try { - const newInterval = parseInterval(interval); - - if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { - res - .status(400) - .json({ error: "Interval must be between 5 minutes and 6 hours." }); - } - - setFetchInterval(newInterval); - res.status(200).json({ message: `Fetch interval set to ${interval}.` }); - } catch (error: unknown) { - const err = error as Error; - logger.error("Error setting fetch interval: " + err.message); - res.status(400).json({ error: "Invalid interval format." }); - } + const ConfHandler = createConfHandler(req, res); + return ConfHandler.scheduler(req); }); /** @@ -142,39 +76,8 @@ router.put("/scheduler", (req: Request, res: Response) => { * description: An error occurred while removing the host. */ router.delete("/removeHost", (req: Request, res: Response): void => { - const hostName = req.query.hostName as string; - - if (!hostName) { - res.status(400).json({ error: "Host name is required." }); - return; - } - - fs.promises - .readFile(configPath, "utf-8") - .then((rawData) => { - const config: DockerConfig = JSON.parse(rawData); - const hostIndex = config.hosts.findIndex( - (host) => host.name === hostName, - ); - - if (hostIndex === -1) { - res.status(404).json({ error: "Host not found." }); - return; - } - - config.hosts.splice(hostIndex, 1); - - return fs.promises - .writeFile(configPath, JSON.stringify(config, null, 2)) - .then(() => { - logger.info(`Removed host: ${hostName}`); - res.status(200).json({ message: "Host removed successfully." }); - }); - }) - .catch((error) => { - logger.error("Error removing host: " + (error as Error).message); - res.status(500).json({ error: "Failed to remove host." }); - }); + const ConfHandler = createConfHandler(req, res); + return ConfHandler.addHost(req); }); export default router; diff --git a/src/typings/dockerConfig.ts b/src/typings/dockerConfig.ts index fea0f4ec..26d72951 100644 --- a/src/typings/dockerConfig.ts +++ b/src/typings/dockerConfig.ts @@ -7,4 +7,5 @@ interface target { interface dockerConfig { hosts: target[]; } + export { dockerConfig, target }; diff --git a/src/typings/syncRequestBody.ts b/src/typings/syncRequestBody.ts new file mode 100644 index 00000000..36fd70a4 --- /dev/null +++ b/src/typings/syncRequestBody.ts @@ -0,0 +1,5 @@ +interface SyncRequestBody { + files: Record; +} + +export { SyncRequestBody }; diff --git a/src/typings/table.ts b/src/typings/table.ts index 4845ebaa..cf0c18ab 100644 --- a/src/typings/table.ts +++ b/src/typings/table.ts @@ -4,4 +4,8 @@ type Table = { timestamp: string; // ISO 8601 formatted datetime string }; -export default Table; +interface DataRow { + info: string; +} + +export { Table, DataRow }; diff --git a/src/typings/template.ts b/src/typings/template.ts new file mode 100644 index 00000000..71e0c8a3 --- /dev/null +++ b/src/typings/template.ts @@ -0,0 +1,5 @@ +interface TemplateData { + text: string; +} + +export { TemplateData }; From c5e4e6c14f447fdb392658960a215ee7af8d6d0d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 19:03:26 +0100 Subject: [PATCH 095/135] Chore: Moving some typings around Fix: Changing to atomic write = no more file system race coditions --- src/config/hostsystem.ts | 3 +- src/config/initFiles.ts | 5 +- src/config/loggerConfig.ts | 63 ------ src/controllers/fetchData.ts | 3 +- src/controllers/highAvailability.ts | 24 +-- src/misc/dependencyGraphs/mermaid-all.txt | 225 +++++++++++++--------- src/typings/atomicWrite.ts | 6 + src/typings/dockerConfig.ts | 26 ++- src/typings/ha.ts | 20 ++ src/typings/hostData.ts | 26 +++ src/typings/response.ts | 6 + src/utils/atomicWrite.ts | 35 ++++ src/utils/connectionChecker.ts | 21 +- src/utils/containerService.ts | 32 +-- src/utils/dockerClient.ts | 17 +- src/utils/extractHostData.ts | 25 +-- src/utils/logger.ts | 3 +- 17 files changed, 273 insertions(+), 267 deletions(-) delete mode 100644 src/config/loggerConfig.ts create mode 100644 src/typings/atomicWrite.ts create mode 100644 src/typings/ha.ts create mode 100644 src/typings/hostData.ts create mode 100644 src/typings/response.ts create mode 100644 src/utils/atomicWrite.ts diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts index 91e44ed5..e9c04de3 100644 --- a/src/config/hostsystem.ts +++ b/src/config/hostsystem.ts @@ -2,6 +2,7 @@ import { RUNNING_IN_DOCKER, VERSION } from "./variables"; import fs from "fs"; import logger from "../utils/logger"; import os from "os"; +import { atomicWrite } from "../utils/atomicWrite"; const userConf = "./src/data/user.conf"; const inDocker: boolean = RUNNING_IN_DOCKER == "true"; @@ -44,7 +45,7 @@ function writeUserConf() { } if (shouldRewriteConfig) { - fs.writeFileSync(userConf, JSON.stringify(installationDetails, null, 2)); + atomicWrite(userConf, JSON.stringify(installationDetails, null, 2)); logger.debug("Configuration file created/updated:", userConf); } diff --git a/src/config/initFiles.ts b/src/config/initFiles.ts index 79822661..008749cb 100644 --- a/src/config/initFiles.ts +++ b/src/config/initFiles.ts @@ -1,5 +1,6 @@ -import { writeFileSync, existsSync } from "fs"; +import { existsSync } from "fs"; import logger from "../utils/logger"; +import { atomicWrite } from "../utils/atomicWrite"; const files = [ { @@ -29,7 +30,7 @@ const files = [ function initFiles(): void { files.forEach(({ path: filePath, content }) => { if (!existsSync(filePath)) { - writeFileSync(filePath, content); + atomicWrite(filePath, content); logger.info(`Created: ${filePath}`); } else { logger.debug(`Skipped (already exists): ${filePath}`); diff --git a/src/config/loggerConfig.ts b/src/config/loggerConfig.ts deleted file mode 100644 index f2b30f43..00000000 --- a/src/config/loggerConfig.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { createLogger, format, transports } from "winston"; -import DailyRotateFile from "winston-daily-rotate-file"; - -const gray = "\x1b[90m"; -const reset = "\x1b[0m"; -const white = "\x1b[97m"; -const red = "\x1b[31m"; -const green = "\x1b[32m"; -const yellow = "\x1b[33m"; -const blue = "\x1b[34m"; - -const ignoreExitListenerLogs = format((info) => { - if ( - typeof info.message === "string" && - info.message.includes("Exit listeners detected") - ) { - return false; // Silences annoying logs - } - return info; -}); - -function colorLog(level: string, levelName: string) { - switch (level) { - case "info": - return `${green}${levelName}${reset}`; - case "debug": - return `${blue}${levelName}${reset}`; - case "error": - return `${red}${levelName}${reset}`; - case "warn": - return `${yellow}${levelName}${reset}`; - default: - return `${gray}UNKNOWN${reset}`; - } -} - -const logger = createLogger({ - level: "debug", - format: format.combine( - ignoreExitListenerLogs(), - format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), - format.printf((info) => { - const level = info.level.toUpperCase().padEnd(5, " "); - const timestamp = `${gray}${info.timestamp}${reset}`; - const levelColorized = colorLog(info.level.toLowerCase(), level); - const message = `${white}${info.message}${reset}`; - - return `${timestamp} ${levelColorized} : ${message}`; - }), - ), - transports: [ - new transports.Console(), - new DailyRotateFile({ - filename: "logs/app-%DATE%.log", - datePattern: "YYYY-MM-DD", - maxSize: "20m", - maxFiles: "14d", - zippedArchive: true, - }), - ], -}); - -export default logger; diff --git a/src/controllers/fetchData.ts b/src/controllers/fetchData.ts index dfc24878..07438ec2 100644 --- a/src/controllers/fetchData.ts +++ b/src/controllers/fetchData.ts @@ -2,6 +2,7 @@ import db from "../config/db"; import fetchAllContainers from "../utils/containerService"; import logger from "../utils/logger"; import fs from "fs"; +import { atomicWrite } from "../utils/atomicWrite"; const filePath = "./src/data/states.json"; let previousState: { [key: string]: string } = {}; @@ -60,7 +61,7 @@ const fetchData = async (): Promise => { // Compare previous and current state if (JSON.stringify(previousState) !== JSON.stringify(containerStatus)) { - fs.writeFileSync(filePath, JSON.stringify(containerStatus, null, 2)); + atomicWrite(filePath, JSON.stringify(containerStatus, null, 2)); logger.info(`Container states saved to ${filePath}`); // TODO: Add logic + notification levels per service } else { diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index c5e3325a..1b28b4f7 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -9,28 +9,11 @@ import { HA_MASTER_IP, HA_NODE, } from "../config/variables"; +import { atomicWrite } from "../utils/atomicWrite"; +import { HighAvailabilityConfig, HaNodeConfig, NodeCache } from "../typings/ha"; const sleep = promisify(setTimeout); -interface HighAvailabilityConfig { - active: boolean; - master: boolean; - nodes: string[]; -} - -interface Node { - ip: string; - id: number; -} - -interface HaNodeConfig { - master: string; -} - -interface NodeCache { - [nodes: string]: Node; -} - const haMasterPath: string = "./src/data/highAvailability.json"; const haNodePath: string = "./src/data/haNode.json"; const nodeCachePath: string = "./src/data/nodeCache.json"; @@ -61,7 +44,6 @@ async function acquireLock(): Promise { } const backoffMs = BASE_DELAY_MS * Math.pow(2, retryCount); - // Add jitter to prevent thundering herd const jitter = Math.random() * 0.3 * backoffMs; const delayMs = backoffMs + jitter; @@ -73,7 +55,7 @@ async function acquireLock(): Promise { } try { - await fs.promises.writeFile(lockFilePath, "locked", { flag: "wx" }); + atomicWrite(lockFilePath, "locked", { exclusive: true }); logger.debug("Lock acquired."); } catch (error) { logger.error(`Error acquiring lock: ${(error as Error).message}`); diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index e81fdf84..e61282b8 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -3,125 +3,160 @@ flowchart LR 0["server.ts"] subgraph 1["controllers"] 2["highAvailability.ts"] -A["proxy.ts"] -B["scheduler.ts"] -D["fetchData.ts"] -T["frontendConfiguration.ts"] +E["proxy.ts"] +F["scheduler.ts"] +H["fetchData.ts"] +V["auth.ts"] +12["frontendConfiguration.ts"] end 3["util"] subgraph 4["config"] 5["variables.ts"] -9["initFiles.ts"] -C["db.ts"] -1F["swaggerConfig.ts"] +D["initFiles.ts"] +G["db.ts"] +1R["swaggerConfig.ts"] end subgraph 6["data"] 7["variables.json"] end -8["init.ts"] -subgraph E["utils"] -F["containerService.ts"] -G["dockerClient.ts"] -J["rateLimitFS.ts"] -W["connectionChecker.ts"] -X["writeOfflineLog.ts"] -subgraph 12["notifications"] -13["_notify.ts"] -14["discord.ts"] -15["_template.ts"] -16["email.ts"] -17["pushbullet.ts"] -18["pushover.ts"] -19["slack.ts"] -1A["telegram.ts"] -1B["whatsapp.ts"] +subgraph 8["typings"] +9["ha.ts"] end -1E["swaggerDocs.ts"] +subgraph A["utils"] +B["atomicWrite.ts"] +I["containerService.ts"] +J["dockerClient.ts"] +O["rateLimitFS.ts"] +16["connectionChecker.ts"] +subgraph 1D["notifications"] +1E["_notify.ts"] +1F["discord.ts"] +1G["_template.ts"] +1H["email.ts"] +1I["pushbullet.ts"] +1J["pushover.ts"] +1K["slack.ts"] +1L["telegram.ts"] +1M["whatsapp.ts"] end -subgraph H["middleware"] -I["authMiddleware.ts"] -K["checkLock.ts"] -L["rateLimiter.ts"] +1Q["swaggerDocs.ts"] end -subgraph M["routes"] -subgraph N["auth"] -O["routes.ts"] +C["init.ts"] +subgraph K["middleware"] +L["authMiddleware.ts"] +P["checkLock.ts"] +Q["rateLimiter.ts"] end -subgraph P["data"] -Q["routes.ts"] +subgraph M["handlers"] +N["response.ts"] +U["auth.ts"] +Y["data.ts"] +11["frontend.ts"] +15["api.ts"] +19["ha.ts"] +1C["notification.ts"] +1P["conf.ts"] end -subgraph R["frontendController"] -S["routes.ts"] +subgraph R["routes"] +subgraph S["auth"] +T["routes.ts"] end -subgraph U["getter"] -V["routes.ts"] +subgraph W["data"] +X["routes.ts"] end -subgraph Y["highavailability"] -Z["routes.ts"] +subgraph Z["frontendController"] +10["routes.ts"] end -subgraph 10["notifications"] -11["routes.ts"] +subgraph 13["getter"] +14["routes.ts"] end -subgraph 1C["setter"] -1D["routes.ts"] +subgraph 17["highavailability"] +18["routes.ts"] +end +subgraph 1A["notifications"] +1B["routes.ts"] +end +subgraph 1N["setter"] +1O["routes.ts"] end end 0-->2 -0-->8 +0-->C 2-->5 +2-->9 +2-->B 2-->3 5-->7 -8-->9 -8-->A -8-->B -8-->I -8-->K -8-->L -8-->O -8-->Q -8-->S -8-->V -8-->Z -8-->11 -8-->1D -8-->1E -A-->5 -B-->C -B-->D -D-->C -D-->F +C-->D +C-->E +C-->F +C-->L +C-->P +C-->Q +C-->T +C-->X +C-->10 +C-->14 +C-->18 +C-->1B +C-->1O +C-->1Q +D-->B +E-->5 F-->G +F-->H +H-->G +H-->B +H-->I +I-->B I-->J -K-->J -Q-->C -S-->T -V-->B -V-->W -V-->F -V-->G -V-->X -Z-->2 -11-->13 -13-->14 -13-->16 -13-->17 -13-->18 -13-->19 -13-->1A -13-->1B -14-->5 +L-->N +L-->O +P-->N +P-->O +T-->U +U-->V +U-->N +X-->Y +Y-->G +Y-->N +10-->11 +11-->12 +11-->N 14-->15 -16-->5 -16-->15 -17-->5 -17-->15 -18-->5 -18-->15 -19-->5 -19-->15 -1A-->5 -1A-->15 -1B-->5 -1B-->15 -1D-->B +15-->F +15-->16 +15-->I +15-->J +15-->N +18-->19 +19-->2 +19-->N +1B-->1C +1C-->1E +1C-->N 1E-->1F +1E-->1H +1E-->1I +1E-->1J +1E-->1K +1E-->1L +1E-->1M +1F-->5 +1F-->1G +1H-->5 +1H-->1G +1I-->5 +1I-->1G +1J-->5 +1J-->1G +1K-->5 +1K-->1G +1L-->5 +1L-->1G +1M-->5 +1M-->1G +1O-->1P +1P-->F +1P-->N +1Q-->1R diff --git a/src/typings/atomicWrite.ts b/src/typings/atomicWrite.ts new file mode 100644 index 00000000..1f4bfb4a --- /dev/null +++ b/src/typings/atomicWrite.ts @@ -0,0 +1,6 @@ +interface AtomicWriteOptions { + mode?: number; + exclusive?: boolean; +} + +export { AtomicWriteOptions }; diff --git a/src/typings/dockerConfig.ts b/src/typings/dockerConfig.ts index 26d72951..a1749d1f 100644 --- a/src/typings/dockerConfig.ts +++ b/src/typings/dockerConfig.ts @@ -8,4 +8,28 @@ interface dockerConfig { hosts: target[]; } -export { dockerConfig, target }; +interface HostConfig { + name: string; + [key: string]: string | number; +} + +interface ContainerData { + name: string; + id: string; + hostName: string; + state: string; + cpu_usage: number; + mem_usage: number; + mem_limit: number; + net_rx: number; + net_tx: number; + current_net_rx: number; + current_net_tx: number; + networkMode: string; +} + +interface AllContainerData { + [hostName: string]: ContainerData[] | { error: string }; +} + +export { dockerConfig, target, ContainerData, AllContainerData, HostConfig }; diff --git a/src/typings/ha.ts b/src/typings/ha.ts new file mode 100644 index 00000000..a722fff8 --- /dev/null +++ b/src/typings/ha.ts @@ -0,0 +1,20 @@ +interface HighAvailabilityConfig { + active: boolean; + master: boolean; + nodes: string[]; +} + +interface Node { + ip: string; + id: number; +} + +interface HaNodeConfig { + master: string; +} + +interface NodeCache { + [nodes: string]: Node; +} + +export { HighAvailabilityConfig, Node, HaNodeConfig, NodeCache }; diff --git a/src/typings/hostData.ts b/src/typings/hostData.ts new file mode 100644 index 00000000..cf5a78da --- /dev/null +++ b/src/typings/hostData.ts @@ -0,0 +1,26 @@ +interface Component { + Name: string; + Version: string; +} + +interface JsonData { + hostName: string; + info: { + ID: string; + Containers: number; + ContainersRunning: number; + ContainersPaused: number; + ContainersStopped: number; + Images: number; + OperatingSystem: string; + KernelVersion: string; + Architecture: string; + MemTotal: number; + NCPU: number; + }; + version: { + Components: Component[]; + }; +} + +export { JsonData }; diff --git a/src/typings/response.ts b/src/typings/response.ts new file mode 100644 index 00000000..b122dfe2 --- /dev/null +++ b/src/typings/response.ts @@ -0,0 +1,6 @@ +interface StatusResponse { + ApiReachable: boolean; + online: { [key: string]: boolean }; +} + +export { StatusResponse }; diff --git a/src/utils/atomicWrite.ts b/src/utils/atomicWrite.ts new file mode 100644 index 00000000..51f33759 --- /dev/null +++ b/src/utils/atomicWrite.ts @@ -0,0 +1,35 @@ +import fs from "fs"; +import logger from "./logger"; +import { AtomicWriteOptions } from "../typings/atomicWrite"; + +export function atomicWrite( + targetPath: string, + data: string | Buffer | Record, + options: AtomicWriteOptions = {}, +): void { + const { mode = 0o600, exclusive = false } = options; + const tempFile = `${targetPath}.tmp`; + + try { + const writeData = + typeof data === "object" && !(data instanceof Buffer) + ? JSON.stringify(data, null, 2) + : data; + + if (exclusive && fs.existsSync(targetPath)) { + throw new Error(`File already exists: ${targetPath}`); + } + + fs.writeFileSync(tempFile, writeData, { mode }); + + fs.renameSync(tempFile, targetPath); + + logger.debug(`File successfully written to: ${targetPath}`); + } catch (error: unknown) { + if (fs.existsSync(tempFile)) fs.unlinkSync(tempFile); + logger.error( + `Failed to write file at ${targetPath}: ${(error as Error).message}`, + ); + throw error; + } +} diff --git a/src/utils/connectionChecker.ts b/src/utils/connectionChecker.ts index 92efba3d..85b00dde 100644 --- a/src/utils/connectionChecker.ts +++ b/src/utils/connectionChecker.ts @@ -1,26 +1,17 @@ import * as fs from "fs"; import * as net from "net"; -import logger from "../config/loggerConfig"; +import logger from "./logger"; +import { target } from "../typings/dockerConfig"; +import { StatusResponse } from "../typings/response"; const filePath: string = "./src/data/dockerConfig.json"; -interface Host { - name: string; - url: string; - port: string; -} - -interface StatusResponse { - ApiReachable: boolean; - online: { [key: string]: boolean }; -} - -async function checkHostStatus(hosts: Host[]): Promise { +async function checkHostStatus(hosts: target[]): Promise { const results: { [key: string]: boolean } = {}; for (const host of hosts) { const { name, url, port } = host; - const isOnline = await checkPort(url, parseInt(port, 10)); + const isOnline = await checkPort(url, port); results[name] = !!isOnline; @@ -65,7 +56,7 @@ async function checkReachability(): Promise { try { const data = fs.readFileSync(filePath, "utf-8"); const parsedData = JSON.parse(data); - const hosts: Host[] = parsedData.hosts; + const hosts: target[] = parsedData.hosts; return await checkHostStatus(hosts); } catch (error: unknown) { logger.error(`Error reading file: ${error as Error}`); diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts index 841e9c2b..f9277c1a 100644 --- a/src/utils/containerService.ts +++ b/src/utils/containerService.ts @@ -2,31 +2,9 @@ import logger from "./logger"; import { ContainerInfo, ContainerStats, ContainerInspectInfo } from "dockerode"; import getDockerClient from "./dockerClient"; import fs from "fs"; +import { atomicWrite } from "./atomicWrite"; const configPath = "./src/data/dockerConfig.json"; - -interface HostConfig { - name: string; - [key: string]: string | number; -} - -interface ContainerData { - name: string; - id: string; - hostName: string; - state: string; - cpu_usage: number; - mem_usage: number; - mem_limit: number; - net_rx: number; - net_tx: number; - current_net_rx: number; - current_net_tx: number; - networkMode: string; -} - -interface AllContainerData { - [hostName: string]: ContainerData[] | { error: string }; -} +import { AllContainerData, HostConfig } from "../typings/dockerConfig"; function loadConfig() { try { @@ -34,11 +12,7 @@ function loadConfig() { logger.warn( `Config file not found. Creating an empty file at ${configPath}`, ); - fs.writeFileSync( - configPath, - JSON.stringify({ hosts: [] }, null, 2), - "utf-8", - ); + atomicWrite(configPath, JSON.stringify({ hosts: [] }, null, 2)); } const configData = fs.readFileSync(configPath, "utf-8"); diff --git a/src/utils/dockerClient.ts b/src/utils/dockerClient.ts index dc0f5e91..8f2718b2 100644 --- a/src/utils/dockerClient.ts +++ b/src/utils/dockerClient.ts @@ -1,24 +1,15 @@ -// src/utils/dockerClient.ts import Docker from "dockerode"; import fs from "fs"; import logger from "./logger"; -interface DockerHostConfig { - name: string; - url: string; - port?: number; -} - -interface DockerConfig { - hosts: DockerHostConfig[]; -} +import { dockerConfig, target } from "../typings/dockerConfig"; -function loadDockerConfig(): DockerConfig { +function loadDockerConfig(): dockerConfig { const configPath = "./src/data/dockerConfig.json"; try { const rawData = fs.readFileSync(configPath, "utf-8"); logger.debug("Refreshed DockerConfig.json"); - return JSON.parse(rawData) as DockerConfig; + return JSON.parse(rawData) as dockerConfig; } catch (error: unknown) { logger.error( "Error loading dockerConfig.json: " + (error as Error).message, @@ -27,7 +18,7 @@ function loadDockerConfig(): DockerConfig { } } -function createDockerClient(hostConfig: DockerHostConfig): Docker { +function createDockerClient(hostConfig: target): Docker { logger.info( `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}`, ); diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts index 25ea0168..0af612ec 100644 --- a/src/utils/extractHostData.ts +++ b/src/utils/extractHostData.ts @@ -1,27 +1,4 @@ -interface Component { - Name: string; - Version: string; -} - -interface JsonData { - hostName: string; - info: { - ID: string; - Containers: number; - ContainersRunning: number; - ContainersPaused: number; - ContainersStopped: number; - Images: number; - OperatingSystem: string; - KernelVersion: string; - Architecture: string; - MemTotal: number; - NCPU: number; - }; - version: { - Components: Component[]; - }; -} +import { JsonData } from "../typings/hostData"; type ComponentMap = Record; diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 8c1ea4a0..d1a3e85a 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,6 +1,5 @@ import { createLogger, format, transports } from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; -import loggerConfig from "../config/loggerConfig"; // ANSI color codes for log level customization const colors = { @@ -42,7 +41,7 @@ const filterLogs = format((info) => { // Logger instance const logger = createLogger({ - level: loggerConfig.level || "debug", + level: "debug", format: format.combine( filterLogs(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), From 604a6cf4b9c765190847fc8b6e512266d2c0454d Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 20:53:29 +0100 Subject: [PATCH 096/135] Chore: Move files around --- README.md | 13 +- package.json | 4 +- .../dependencyGraphs}/.dependency-cruiser.cjs | 0 .../dependencyGraphs/createDependencyGraph.sh | 41 ++++ src/misc/dependencyGraphs/mermaid-all.txt | 217 +++++++----------- src/misc/dependencyGraphs/mermaid-api.txt | 44 ++-- src/misc/dependencyGraphs/mermaid-auth.txt | 21 +- src/misc/dependencyGraphs/mermaid-conf.txt | 36 +-- src/misc/dependencyGraphs/mermaid-data.txt | 22 +- .../dependencyGraphs/mermaid-frontend.txt | 22 +- src/misc/dependencyGraphs/mermaid-ha.txt | 34 ++- .../mermaid-notificationService.txt | 38 +-- src/{utils => misc}/removeUnusedDeps.sh | 0 src/utils/createDependencyGraph.sh | 38 --- 14 files changed, 253 insertions(+), 277 deletions(-) rename src/{ => misc/dependencyGraphs}/.dependency-cruiser.cjs (100%) create mode 100755 src/misc/dependencyGraphs/createDependencyGraph.sh rename src/{utils => misc}/removeUnusedDeps.sh (100%) delete mode 100755 src/utils/createDependencyGraph.sh diff --git a/README.md b/README.md index 4e6daf38..f8e330cd 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,15 @@ ![Dockstat Logo](.github/DockStat.png) -_Pipelines:_
-[![Docker Image CI](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml/badge.svg?branch=main)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml)
-[![Build dockstatapi:nightly](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml)
-[![Tests](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml/badge.svg?branch=dev)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) +

+ +# Pipelines + +[![Docker Image CI](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/build-image.yml?branch=main&label=Docker%20Image%20CI&style=for-the-badge&logo=docker)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml) +[![Build dockstatapi:nightly](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/build-dev.yaml?branch=dev&label=Nightly%20Build&style=for-the-badge&logo=github)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml) +[![Validate](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/validation.yml?branch=dev&label=Validation&style=for-the-badge&logo=checkmarx)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) + +
This specific branch contains the currently WIP **DockStatAPI-v2**, this update will bring major breaking changes so please be careful. With this new release a couple of extra features (compared to v1) are going to be available. diff --git a/package.json b/package.json index 6dd43aba..29e307e6 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "start:build": "npx tsc && node dist/server.js", "dev": "npm run local-env-file && nodemon", "dev:trace": "npm run local-env-file && nodemon --trace-uncaught --trace-warnings", - "dep": "bash ./src/utils/createDependencyGraph.sh", - "dep:remove": "bash ./src/utils/removeUnusedDeps.sh && npm run dep", + "dep": "bash ./src/misc/dependencyGraphs/createDependencyGraph.sh", + "dep:remove": "bash ./src/misc/removeUnusedDeps.sh && npm run dep", "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", "mini": "bash ./src/misc/minifyDist.sh", diff --git a/src/.dependency-cruiser.cjs b/src/misc/dependencyGraphs/.dependency-cruiser.cjs similarity index 100% rename from src/.dependency-cruiser.cjs rename to src/misc/dependencyGraphs/.dependency-cruiser.cjs diff --git a/src/misc/dependencyGraphs/createDependencyGraph.sh b/src/misc/dependencyGraphs/createDependencyGraph.sh new file mode 100755 index 00000000..4e118194 --- /dev/null +++ b/src/misc/dependencyGraphs/createDependencyGraph.sh @@ -0,0 +1,41 @@ +#!/bin/bash +TMP=$(mktemp) +IGNORE="node_modules|logger|.dependency-cruiser|path|fs|os|https|net|process|util" + +cat ./src/init.ts | grep "./routes" | awk '{print $2,$4}' > $TMP + +spawn_worker(){ + local line="$1" + local target_route="$(echo "$line" | cut -d '"' -f2 | sed 's|^./routes|./src/routes|').ts" + local route=$(echo "$line" | awk '{print $1}') + + echo -e "\nRoute: $route \n${target_route}" + + npx depcruise \ + -c ./src/misc/dependencyGraphs/.dependency-cruiser.cjs \ + -p cli-feedback \ + -T mermaid \ + -x "$IGNORE" \ + -f ./src/misc/dependencyGraphs/mermaid-${route}.txt \ + ${target_route} || exit 1 +} + +while read line; do + spawn_worker "$line" & +done < <(cat $TMP) + +npx depcruise \ + -c ./src/misc/dependencyGraphs/.dependency-cruiser.cjs \ + -p cli-feedback \ + -T mermaid \ + -x "$IGNORE" \ + -f ./src/misc/dependencyGraphs/mermaid-all.txt \ + ./src/server.ts || exit 1 + +wait + +find ./src/misc/dependencyGraphs -type f -name "*.txt" -exec sed -i 's/flowchart LR/flowchart TB/g' {} + + +echo -e "\n========\n\n DONE\n\n========" + +exit 0 diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index e61282b8..ad02a822 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -1,20 +1,19 @@ -flowchart LR +flowchart TB -0["server.ts"] -subgraph 1["controllers"] -2["highAvailability.ts"] -E["proxy.ts"] -F["scheduler.ts"] -H["fetchData.ts"] -V["auth.ts"] -12["frontendConfiguration.ts"] +subgraph 0["src"] +1["server.ts"] +subgraph 2["controllers"] +3["highAvailability.ts"] +C["proxy.ts"] +D["scheduler.ts"] +F["fetchData.ts"] +Q["auth.ts"] +X["frontendConfiguration.ts"] end -3["util"] subgraph 4["config"] 5["variables.ts"] -D["initFiles.ts"] -G["db.ts"] -1R["swaggerConfig.ts"] +B["initFiles.ts"] +E["db.ts"] end subgraph 6["data"] 7["variables.json"] @@ -22,141 +21,87 @@ end subgraph 8["typings"] 9["ha.ts"] end -subgraph A["utils"] -B["atomicWrite.ts"] -I["containerService.ts"] -J["dockerClient.ts"] -O["rateLimitFS.ts"] -16["connectionChecker.ts"] -subgraph 1D["notifications"] -1E["_notify.ts"] -1F["discord.ts"] -1G["_template.ts"] -1H["email.ts"] -1I["pushbullet.ts"] -1J["pushover.ts"] -1K["slack.ts"] -1L["telegram.ts"] -1M["whatsapp.ts"] +A["init.ts"] +subgraph G["middleware"] +H["authMiddleware.ts"] +K["checkLock.ts"] +L["rateLimiter.ts"] end -1Q["swaggerDocs.ts"] +subgraph I["handlers"] +J["response.ts"] +P["auth.ts"] +T["data.ts"] +W["frontend.ts"] +10["api.ts"] +13["ha.ts"] +16["notification.ts"] +19["conf.ts"] end -C["init.ts"] -subgraph K["middleware"] -L["authMiddleware.ts"] -P["checkLock.ts"] -Q["rateLimiter.ts"] +subgraph M["routes"] +subgraph N["auth"] +O["routes.ts"] end -subgraph M["handlers"] -N["response.ts"] -U["auth.ts"] -Y["data.ts"] -11["frontend.ts"] -15["api.ts"] -19["ha.ts"] -1C["notification.ts"] -1P["conf.ts"] +subgraph R["data"] +S["routes.ts"] end -subgraph R["routes"] -subgraph S["auth"] -T["routes.ts"] +subgraph U["frontendController"] +V["routes.ts"] end -subgraph W["data"] -X["routes.ts"] +subgraph Y["getter"] +Z["routes.ts"] end -subgraph Z["frontendController"] -10["routes.ts"] +subgraph 11["highavailability"] +12["routes.ts"] end -subgraph 13["getter"] -14["routes.ts"] +subgraph 14["notifications"] +15["routes.ts"] end -subgraph 17["highavailability"] +subgraph 17["setter"] 18["routes.ts"] end -subgraph 1A["notifications"] -1B["routes.ts"] end -subgraph 1N["setter"] -1O["routes.ts"] end -end -0-->2 -0-->C -2-->5 -2-->9 -2-->B -2-->3 +1-->3 +1-->A +3-->5 +3-->9 5-->7 -C-->D -C-->E -C-->F -C-->L -C-->P -C-->Q -C-->T -C-->X -C-->10 -C-->14 -C-->18 -C-->1B -C-->1O -C-->1Q -D-->B -E-->5 -F-->G -F-->H -H-->G -H-->B -H-->I -I-->B -I-->J -L-->N -L-->O -P-->N -P-->O -T-->U -U-->V -U-->N -X-->Y -Y-->G -Y-->N -10-->11 -11-->12 -11-->N -14-->15 -15-->F +A-->B +A-->C +A-->D +A-->H +A-->K +A-->L +A-->O +A-->S +A-->V +A-->Z +A-->12 +A-->15 +A-->18 +C-->5 +D-->E +D-->F +F-->E +H-->J +K-->J +O-->P +P-->Q +P-->J +S-->T +T-->E +T-->J +V-->W +W-->X +W-->J +Z-->10 +10-->D +10-->J +12-->13 +13-->3 +13-->J 15-->16 -15-->I -15-->J -15-->N +16-->J 18-->19 -19-->2 -19-->N -1B-->1C -1C-->1E -1C-->N -1E-->1F -1E-->1H -1E-->1I -1E-->1J -1E-->1K -1E-->1L -1E-->1M -1F-->5 -1F-->1G -1H-->5 -1H-->1G -1I-->5 -1I-->1G -1J-->5 -1J-->1G -1K-->5 -1K-->1G -1L-->5 -1L-->1G -1M-->5 -1M-->1G -1O-->1P -1P-->F -1P-->N -1Q-->1R +19-->D +19-->J diff --git a/src/misc/dependencyGraphs/mermaid-api.txt b/src/misc/dependencyGraphs/mermaid-api.txt index e7c85cc8..3cb4811e 100644 --- a/src/misc/dependencyGraphs/mermaid-api.txt +++ b/src/misc/dependencyGraphs/mermaid-api.txt @@ -1,32 +1,26 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["getter"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["getter"] +3["routes.ts"] end end -subgraph 3["controllers"] -4["scheduler.ts"] -7["fetchData.ts"] +subgraph 4["handlers"] +5["api.ts"] +B["response.ts"] end -subgraph 5["config"] -6["db.ts"] +subgraph 6["controllers"] +7["scheduler.ts"] +A["fetchData.ts"] end -subgraph 8["utils"] -9["containerService.ts"] -A["dockerClient.ts"] -B["connectionChecker.ts"] -C["extractHostData.ts"] -D["writeOfflineLog.ts"] +subgraph 8["config"] +9["db.ts"] end -2-->4 -2-->B -2-->9 -2-->A -2-->C -2-->D -4-->6 -4-->7 -7-->6 +end +3-->5 +5-->7 +5-->B 7-->9 -9-->A +7-->A +A-->9 diff --git a/src/misc/dependencyGraphs/mermaid-auth.txt b/src/misc/dependencyGraphs/mermaid-auth.txt index aaeb683b..336ddedb 100644 --- a/src/misc/dependencyGraphs/mermaid-auth.txt +++ b/src/misc/dependencyGraphs/mermaid-auth.txt @@ -1,8 +1,19 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["auth"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["auth"] +3["routes.ts"] end end - +subgraph 4["handlers"] +5["auth.ts"] +8["response.ts"] +end +subgraph 6["controllers"] +7["auth.ts"] +end +end +3-->5 +5-->7 +5-->8 diff --git a/src/misc/dependencyGraphs/mermaid-conf.txt b/src/misc/dependencyGraphs/mermaid-conf.txt index ba9ca669..370dd892 100644 --- a/src/misc/dependencyGraphs/mermaid-conf.txt +++ b/src/misc/dependencyGraphs/mermaid-conf.txt @@ -1,24 +1,26 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["setter"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["setter"] +3["routes.ts"] end end -subgraph 3["controllers"] -4["scheduler.ts"] -7["fetchData.ts"] +subgraph 4["handlers"] +5["conf.ts"] +B["response.ts"] end -subgraph 5["config"] -6["db.ts"] +subgraph 6["controllers"] +7["scheduler.ts"] +A["fetchData.ts"] end -subgraph 8["utils"] -9["containerService.ts"] -A["dockerClient.ts"] +subgraph 8["config"] +9["db.ts"] end -2-->4 -4-->6 -4-->7 -7-->6 +end +3-->5 +5-->7 +5-->B 7-->9 -9-->A +7-->A +A-->9 diff --git a/src/misc/dependencyGraphs/mermaid-data.txt b/src/misc/dependencyGraphs/mermaid-data.txt index 107d46af..4aa6a133 100644 --- a/src/misc/dependencyGraphs/mermaid-data.txt +++ b/src/misc/dependencyGraphs/mermaid-data.txt @@ -1,11 +1,19 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["data"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["data"] +3["routes.ts"] end end -subgraph 3["config"] -4["db.ts"] +subgraph 4["handlers"] +5["data.ts"] +8["response.ts"] end -2-->4 +subgraph 6["config"] +7["db.ts"] +end +end +3-->5 +5-->7 +5-->8 diff --git a/src/misc/dependencyGraphs/mermaid-frontend.txt b/src/misc/dependencyGraphs/mermaid-frontend.txt index 03340053..8dde5ce9 100644 --- a/src/misc/dependencyGraphs/mermaid-frontend.txt +++ b/src/misc/dependencyGraphs/mermaid-frontend.txt @@ -1,11 +1,19 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["frontendController"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["frontendController"] +3["routes.ts"] end end -subgraph 3["controllers"] -4["frontendConfiguration.ts"] +subgraph 4["handlers"] +5["frontend.ts"] +8["response.ts"] end -2-->4 +subgraph 6["controllers"] +7["frontendConfiguration.ts"] +end +end +3-->5 +5-->7 +5-->8 diff --git a/src/misc/dependencyGraphs/mermaid-ha.txt b/src/misc/dependencyGraphs/mermaid-ha.txt index ce156053..2c789f6c 100644 --- a/src/misc/dependencyGraphs/mermaid-ha.txt +++ b/src/misc/dependencyGraphs/mermaid-ha.txt @@ -1,11 +1,31 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["highavailability"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["highavailability"] +3["routes.ts"] end end -subgraph 3["controllers"] -4["highAvailability.ts"] +subgraph 4["handlers"] +5["ha.ts"] +E["response.ts"] end -2-->4 +subgraph 6["controllers"] +7["highAvailability.ts"] +end +subgraph 8["config"] +9["variables.ts"] +end +subgraph A["data"] +B["variables.json"] +end +subgraph C["typings"] +D["ha.ts"] +end +end +3-->5 +5-->7 +5-->E +7-->9 +7-->D +9-->B diff --git a/src/misc/dependencyGraphs/mermaid-notificationService.txt b/src/misc/dependencyGraphs/mermaid-notificationService.txt index cef6c2cd..2bc9731c 100644 --- a/src/misc/dependencyGraphs/mermaid-notificationService.txt +++ b/src/misc/dependencyGraphs/mermaid-notificationService.txt @@ -1,35 +1,15 @@ -flowchart LR +flowchart TB -subgraph 0["routes"] -subgraph 1["notifications"] -2["routes.ts"] +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["notifications"] +3["routes.ts"] end end -subgraph 3["utils"] -subgraph 4["notifications"] -5["_notify.ts"] -6["discord.ts"] -7["_template.ts"] -8["email.ts"] -9["pushbullet.ts"] -A["pushover.ts"] -B["slack.ts"] -C["telegram.ts"] -D["whatsapp.ts"] +subgraph 4["handlers"] +5["notification.ts"] +6["response.ts"] end end -2-->5 +3-->5 5-->6 -5-->8 -5-->9 -5-->A -5-->B -5-->C -5-->D -6-->7 -8-->7 -9-->7 -A-->7 -B-->7 -C-->7 -D-->7 diff --git a/src/utils/removeUnusedDeps.sh b/src/misc/removeUnusedDeps.sh similarity index 100% rename from src/utils/removeUnusedDeps.sh rename to src/misc/removeUnusedDeps.sh diff --git a/src/utils/createDependencyGraph.sh b/src/utils/createDependencyGraph.sh deleted file mode 100755 index 9c220f7a..00000000 --- a/src/utils/createDependencyGraph.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -cd src || exit 1 -TMP=$(mktemp) -IGNORE="../node_modules|logger|.dependency-cruiser|path|fs|os|https|net|process" - -cat ./server.ts | grep "./routes" | awk '{print $2,$4}' > $TMP - -spawn_worker(){ - local line="$1" - local target_route="$(echo "$line" | cut -d '"' -f2).ts" - local route=$(echo "$line" | awk '{print $1}') - - echo -e "\nRoute: $route \n${target_route}" - - npx depcruise \ - -p cli-feedback \ - -T mermaid \ - -x "$IGNORE" \ - -f ./misc/dependencyGraphs/mermaid-${route}.txt \ - ${target_route} || exit 1 -} - -while read line; do - spawn_worker "$line" & -done < <(cat $TMP) - -npx depcruise \ - -p cli-feedback \ - -T mermaid \ - -x "$IGNORE" \ - -f ./misc/dependencyGraphs/mermaid-all.txt \ - ./server.ts || exit 1 - -wait - -echo -e "\n========\n\n DONE\n\n========" - -exit 0 From dcf62b70cf38b757b28a3c8f2af741f00947c470 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 20:54:44 +0100 Subject: [PATCH 097/135] Chore: Update ReadMe --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f8e330cd..8fc800a8 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,7 @@ # Pipelines [![Docker Image CI](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/build-image.yml?branch=main&label=Docker%20Image%20CI&style=for-the-badge&logo=docker)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml) -[![Build dockstatapi:nightly](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/build-dev.yaml?branch=dev&label=Nightly%20Build&style=for-the-badge&logo=github)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-dev.yaml) -[![Validate](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/validation.yml?branch=dev&label=Validation&style=for-the-badge&logo=checkmarx)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) +[![Validation](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/validation.yml?branch=dev&label=Validation&style=for-the-badge&logo=checkmarx)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) From 45b0cfd8c58726c35321d4c89aa9a0610450dd64 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 1 Jan 2025 20:58:35 +0100 Subject: [PATCH 098/135] Fix: Remove escape characters from log file --- src/utils/logger.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/utils/logger.ts b/src/utils/logger.ts index d1a3e85a..00adbdfc 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -45,23 +45,35 @@ const logger = createLogger({ format: format.combine( filterLogs(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), - format.printf((info) => { - const level = info.level.toUpperCase().padEnd(5, " "); - const timestamp = `${colors.gray}${info.timestamp}${colors.reset}`; - const levelColorized = colorizeLogLevel(info.level.toLowerCase(), level); - const message = `${colors.white}${info.message}${colors.reset}`; - - return `${timestamp} ${levelColorized} : ${message}`; - }), ), transports: [ - new transports.Console(), + new transports.Console({ + format: format.combine( + format.printf((info) => { + const level = info.level.toUpperCase().padEnd(5, " "); + const timestamp = `${colors.gray}${info.timestamp}${colors.reset}`; + const levelColorized = colorizeLogLevel( + info.level.toLowerCase(), + level, + ); + const message = `${colors.white}${info.message}${colors.reset}`; + + return `${timestamp} ${levelColorized} : ${message}`; + }), + ), + }), new DailyRotateFile({ filename: "logs/app-%DATE%.log", datePattern: "YYYY-MM-DD", maxSize: "20m", maxFiles: "14d", zippedArchive: true, + format: format.combine( + format.printf((info) => { + const level = info.level.toUpperCase().padEnd(5, " "); + return `${info.timestamp} ${level} : ${info.message}`; + }), + ), }), ], }); From ffb45521f031c00e96a804744342cc155af58ae2 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 2 Jan 2025 21:56:01 +0100 Subject: [PATCH 099/135] Chore: Cleaning up Dockerfile(s) --- .github/workflows/build-image.yaml | 2 + .github/workflows/validation.yaml | 16 +- .gitignore | 3 +- Dockerfile | 61 ----- Dockerfile-dev | 61 ----- TODO.md | 5 +- docker/Dockerfile-base | 59 +++++ docker/Dockerfile-dev | 59 +++++ .../docker-compose.yaml | 10 +- package-lock.json | 247 ++++++++++-------- package.json | 8 +- src/config/hostsystem.ts | 24 +- src/controllers/highAvailability.ts | 4 +- src/misc/.tmux.sh | 1 + src/server.ts | 5 - 15 files changed, 306 insertions(+), 259 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Dockerfile-dev create mode 100644 docker/Dockerfile-base create mode 100644 docker/Dockerfile-dev rename docker-compose.yaml => docker/docker-compose.yaml (87%) create mode 100644 src/misc/.tmux.sh diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 720bed85..41f18cb8 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -39,6 +39,8 @@ jobs: uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64 + context: . + file: docker/Dockerfile-base push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index dfd9330a..2226171e 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -103,7 +103,7 @@ jobs: - uses: actions/checkout@v4 - name: Build the Container image - run: docker build . --file Dockerfile --tag localbuild/testimage:latest + run: docker build . --file docker/Dockerfile-base --tag localbuild/testimage:latest - name: Run Grype test run: grype -o sarif localbuild/testimage:latest > results.sarif @@ -126,16 +126,6 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - - name: Set up Node.js version from .nvmrc - run: | - curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash - export NVM_DIR="$HOME/.nvm" - [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh" - nvm install - nvm use - node -v - npm -v - - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -160,6 +150,8 @@ jobs: - name: Build and Push Docker Images uses: docker/build-push-action@v6 with: + context: . + file: docker/Dockerfile-base platforms: linux/amd64,linux/arm64 push: false tags: ${{ steps.metadata.outputs.tags }} @@ -205,6 +197,8 @@ jobs: uses: docker/build-push-action@v6 with: platforms: linux/amd64,linux/arm64, + context: . + file: docker/Dockerfile-dev push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} diff --git a/.gitignore b/.gitignore index 84449de1..dc93b889 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ # custom paths: src/data/* -docker +docker/master +docker/slave .test* # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 0e59a459..00000000 --- a/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -# Stage 1: Build stage -FROM node:alpine AS builder - -LABEL maintainer="https://github.com/its4nik" -LABEL version="2.0.1" -LABEL description="API for DockStat" -LABEL license="BSD-3-Clause license" -LABEL repository="https://github.com/its4nik/dockstatapi" -LABEL documentation="https://github.com/its4nik/dockstatapi" -LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" -LABEL org.opencontainers.image.licenses="BSD-3-Clause license" -LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" - -WORKDIR /build -ENV NODE_NO_WARNINGS=1 - -RUN apk add --update --no-cache bash - -COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ -RUN npm install - -COPY ./src ./src -RUN mv ./src/sample-variable.json ./src/data/variables.json -RUN npm run build:mini - -# Stage 2: main stage -FROM alpine AS main - -RUN apk add --update npm - -WORKDIR /build - -RUN mkdir -p /build/src/data - -COPY package*.json ./ -RUN npm install --omit=dev - -COPY --from=builder /build/dist/* /build/src -COPY --from=builder /build/src/misc/entrypoint.sh /build/entrypoint.sh -COPY --from=builder /build/src/misc/createEnvFile.sh /build/createEnvFile.sh - -RUN node src/config/db.js - -# Stage 3: Production stage -FROM alpine AS production - -WORKDIR /api - -RUN apk add --update --no-cache bash curl nodejs && \ - adduser -h /api -s /bin/bash -D dockstatapi dockstatapi && \ - chown -hR dockstatapi:dockstatapi /api - -USER dockstatapi - -HEALTHCHECK --interval=5m --timeout=3s \ - CMD curl -f http://localhost:9876/api/status || exit 1 - -COPY --chown=dockstatapi:dockstatapi --from=main /build /api - -EXPOSE 9876 -ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/Dockerfile-dev b/Dockerfile-dev deleted file mode 100644 index ba9c01cb..00000000 --- a/Dockerfile-dev +++ /dev/null @@ -1,61 +0,0 @@ -# Stage 1: Build stage -FROM node:alpine AS builder - -LABEL maintainer="https://github.com/its4nik" -LABEL version="2.0.1" -LABEL description="API for DockStat" -LABEL license="BSD-3-Clause license" -LABEL repository="https://github.com/its4nik/dockstatapi" -LABEL documentation="https://github.com/its4nik/dockstatapi" -LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" -LABEL org.opencontainers.image.licenses="BSD-3-Clause license" -LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" - -WORKDIR /build -ENV NODE_NO_WARNINGS=1 - -RUN apk add --update --no-cache bash - -COPY tsconfig.json environment.d.ts package*.json tsconfig.json ./ -RUN npm install - -COPY ./src ./src -RUN mv ./src/sample-variable.json ./src/data/variables.json -RUN npm run build - -# Stage 2: main stage -FROM alpine AS main - -RUN apk add --update npm - -WORKDIR /build - -RUN mkdir -p /build/src/data - -COPY package*.json ./ -RUN npm install --omit=dev - -COPY --from=builder /build/dist/* /build/src -COPY --from=builder /build/src/misc/entrypoint.sh /build/entrypoint.sh -COPY --from=builder /build/src/misc/createEnvFile.sh /build/createEnvFile.sh - -RUN node src/config/db.js - -# Stage 3: Production stage -FROM alpine AS production - -WORKDIR /api - -RUN apk add --update --no-cache bash curl nodejs && \ - adduser -h /api -s /bin/bash -D dockstatapi dockstatapi && \ - chown -hR dockstatapi:dockstatapi /api - -USER dockstatapi - -HEALTHCHECK --interval=5m --timeout=3s \ - CMD curl -f http://localhost:9876/api/status || exit 1 - -COPY --chown=dockstatapi:dockstatapi --from=main /build /api - -EXPOSE 9876 -ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/TODO.md b/TODO.md index fc40ce6f..7ac3d438 100644 --- a/TODO.md +++ b/TODO.md @@ -12,5 +12,6 @@ - [x] Update notification service - [x] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) - [ ] Better project structure -- [ ] Update logging => Better errors -- [ ] Update json responses and swagger +- [x] Update logging => Better errors +- [x] Update json responses +- [ ] Swagger update diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base new file mode 100644 index 00000000..1f9bf30d --- /dev/null +++ b/docker/Dockerfile-base @@ -0,0 +1,59 @@ +# Stage 1: Build stage +FROM node:alpine AS builder + +LABEL maintainer="https://github.com/its4nik" +LABEL version="2.0.1" +LABEL description="API for DockStat" +LABEL license="BSD-3-Clause license" +LABEL repository="https://github.com/its4nik/dockstatapi" +LABEL documentation="https://github.com/its4nik/dockstatapi" +LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" +LABEL org.opencontainers.image.licenses="BSD-3-Clause license" +LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" + +WORKDIR /app + +ENV NODE_NO_WARNINGS=1 + +RUN apk add --no-cache bash + +COPY tsconfig.json environment.d.ts package*.json ./ + +RUN export npm_config_cache=$(mktemp -d) && \ + npm install --production=false && \ + rm -rf $npm_config_cache /tmp/*.log + +COPY ./src ./src +RUN mv ./src/sample-variable.json ./src/data/variables.json +RUN npm run build:mini + +# Stage 2: Production stage +FROM node:alpine AS production + +WORKDIR /api + +RUN apk add --no-cache bash curl && \ + adduser -h /api -s /bin/bash -D dockstatapi + +HEALTHCHECK --interval=5m --timeout=3s \ + CMD curl -f http://localhost:9876/api/status || exit 1 + +COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src +COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ + +RUN export npm_config_cache=$(mktemp -d) && \ + npm install --omit=dev && \ + rm -rf $npm_config_cache /tmp/*.log + +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh +RUN chmod +x /api/*.sh + +EXPOSE 9876 + +RUN chmod -R 777 /api/src/data /api && \ + chown -R dockstatapi:dockstatapi /api + +STOPSIGNAL 130 +USER dockstatapi +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev new file mode 100644 index 00000000..58b9f43d --- /dev/null +++ b/docker/Dockerfile-dev @@ -0,0 +1,59 @@ +# Stage 1: Build stage +FROM node:alpine AS builder + +LABEL maintainer="https://github.com/its4nik" +LABEL version="2.0.1" +LABEL description="API for DockStat" +LABEL license="BSD-3-Clause license" +LABEL repository="https://github.com/its4nik/dockstatapi" +LABEL documentation="https://github.com/its4nik/dockstatapi" +LABEL org.opencontainers.image.description="The DockSatAPI is a free and OpenSource backend for gathering container statistics across hosts" +LABEL org.opencontainers.image.licenses="BSD-3-Clause license" +LABEL org.opencontainers.image.source="https://github.com/its4nik/dockstatapi" + +WORKDIR /app + +ENV NODE_NO_WARNINGS=1 + +RUN apk add --no-cache bash + +COPY tsconfig.json environment.d.ts package*.json ./ + +RUN export npm_config_cache=$(mktemp -d) && \ + npm install --production=false && \ + rm -rf $npm_config_cache /tmp/*.log + +COPY ./src ./src +RUN mv ./src/sample-variable.json ./src/data/variables.json +RUN npm run build + +# Stage 2: Production stage +FROM node:alpine AS production + +WORKDIR /api + +RUN apk add --no-cache bash curl && \ + adduser -h /api -s /bin/bash -D dockstatapi + +HEALTHCHECK --interval=5m --timeout=3s \ + CMD curl -f http://localhost:9876/api/status || exit 1 + +COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src +COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ + +RUN export npm_config_cache=$(mktemp -d) && \ + npm install --omit=dev && \ + rm -rf $npm_config_cache /tmp/*.log + +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh +RUN chmod +x /api/*.sh + +EXPOSE 9876 + +RUN chmod -R 777 /api/src/data /api && \ + chown -R dockstatapi:dockstatapi /api + +STOPSIGNAL 130 +USER dockstatapi +ENTRYPOINT [ "bash", "./entrypoint.sh" ] diff --git a/docker-compose.yaml b/docker/docker-compose.yaml similarity index 87% rename from docker-compose.yaml rename to docker/docker-compose.yaml index 4789b71c..225c5de2 100644 --- a/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -5,14 +5,16 @@ networks: services: master: container_name: master + user: "${UID:-1000}:${GID:-1000}" environment: - NODE_ENV=development - - HA_MASTER=false + - HA_MASTER=true - HA_MASTER_IP=master:9876 - HA_NODE=slave:9876 - HA_UNSAFE=true volumes: - - ./docker/master:/api/src/data + - ./master/data:/api/src/data + - ./master/logs:/api/logs ports: - 9876:9876 image: dockstatapi:local @@ -24,10 +26,12 @@ services: slave: container_name: slave + user: "${UID:-1000}:${GID:-1000}" environment: - NODE_ENV=development volumes: - - ./docker/slave:/api/src/data + - ./slave/data:/api/src/data + - ./slave/logs:/api/logs ports: - 6789:9876 image: dockstatapi:local diff --git a/package-lock.json b/package-lock.json index 1310ab8b..8c1ea14f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -872,26 +872,6 @@ "node-pre-gyp": "bin/node-pre-gyp" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -956,6 +936,19 @@ "node": ">=10" } }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@playwright/test": { "version": "1.49.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", @@ -1109,9 +1102,9 @@ "license": "MIT" }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz", - "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.3.tgz", + "integrity": "sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==", "dev": true, "license": "MIT", "dependencies": { @@ -1142,9 +1135,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", - "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "version": "22.10.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz", + "integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==", "dev": true, "license": "MIT", "dependencies": { @@ -1209,9 +1202,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.68", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.68.tgz", - "integrity": "sha512-QGtpFH1vB99ZmTa63K4/FU8twThj4fuVSBkGddTp7uIL/cuoLWIUSL2RcOaigBhfR+hg5pgGkBnkoOxrTVBMKw==", + "version": "18.19.69", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.69.tgz", + "integrity": "sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1257,17 +1250,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz", - "integrity": "sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", + "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/type-utils": "8.18.2", - "@typescript-eslint/utils": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/type-utils": "8.19.0", + "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1287,16 +1280,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz", - "integrity": "sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", + "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/typescript-estree": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "debug": "^4.3.4" }, "engines": { @@ -1312,14 +1305,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz", - "integrity": "sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", + "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2" + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1330,14 +1323,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz", - "integrity": "sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", + "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.18.2", - "@typescript-eslint/utils": "8.18.2", + "@typescript-eslint/typescript-estree": "8.19.0", + "@typescript-eslint/utils": "8.19.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1354,9 +1347,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz", - "integrity": "sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", + "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", "dev": true, "license": "MIT", "engines": { @@ -1368,14 +1361,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz", - "integrity": "sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", + "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/visitor-keys": "8.18.2", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/visitor-keys": "8.19.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -1395,16 +1388,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz", - "integrity": "sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", + "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.18.2", - "@typescript-eslint/types": "8.18.2", - "@typescript-eslint/typescript-estree": "8.18.2" + "@typescript-eslint/scope-manager": "8.19.0", + "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/typescript-estree": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1419,13 +1412,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz", - "integrity": "sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", + "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.18.2", + "@typescript-eslint/types": "8.19.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1537,9 +1530,9 @@ } }, "node_modules/agentkeepalive": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz", - "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", "license": "MIT", "optional": true, "dependencies": { @@ -1919,6 +1912,19 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", @@ -3238,21 +3244,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", + "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3261,6 +3267,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.0.tgz", + "integrity": "sha512-TtLgOcKaF1nMP2ijJnITkE4nRhbpshHhmzKiuhmSniiwWzovoqwqQ8rNuhf0mXJOqIY5iU+QkUe0CkJYrLsG9w==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-tsconfig": { "version": "4.8.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", @@ -4014,19 +4033,6 @@ "node": ">=4" } }, - "node_modules/license-checker/node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/license-checker/node_modules/nopt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", @@ -4505,15 +4511,16 @@ } }, "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, "bin": { "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/mkdirp-classic": { @@ -4584,6 +4591,26 @@ "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", @@ -6533,6 +6560,18 @@ "node": ">=8" } }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/teamcity-service-messages": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz", @@ -6786,15 +6825,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.18.2.tgz", - "integrity": "sha512-KuXezG6jHkvC3MvizeXgupZzaG5wjhU3yE8E7e6viOvAvD9xAWYp8/vy0WULTGe9DYDWcQu7aW03YIV3mSitrQ==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz", + "integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.18.2", - "@typescript-eslint/parser": "8.18.2", - "@typescript-eslint/utils": "8.18.2" + "@typescript-eslint/eslint-plugin": "8.19.0", + "@typescript-eslint/parser": "8.19.0", + "@typescript-eslint/utils": "8.19.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" diff --git a/package.json b/package.json index 29e307e6..6b38fd68 100644 --- a/package.json +++ b/package.json @@ -6,18 +6,16 @@ "scripts": { "local-env-file": "bash ./src/misc/createEnvDev.sh", "start": "npm run local-env-file && tsx src/server.ts", - "start:build": "npx tsc && node dist/server.js", "dev": "npm run local-env-file && nodemon", "dev:trace": "npm run local-env-file && nodemon --trace-uncaught --trace-warnings", "dep": "bash ./src/misc/dependencyGraphs/createDependencyGraph.sh", "dep:remove": "bash ./src/misc/removeUnusedDeps.sh && npm run dep", "build": "npx tsc", "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", + "build:docker": "docker build . -t \"dockstatapi:local\" -f ./docker/Dockerfile-dev", "mini": "bash ./src/misc/minifyDist.sh", - "docker": "docker compose up -d", - "docker:full": "docker compose up -d && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", - "docker:build": "docker build . -t \"dockstatapi:local\" -f ./Dockerfile-dev && docker compose up -d", - "docker:build:full": "npm run docker:build && [ -z \"$TMUX\" ] && tmux new-session -d -s docker 'docker compose up -d && docker compose logs -f master' \\; split-window -v 'docker compose logs -f slave' \\; attach-session || echo 'Already inside a tmux session. Exiting.'; docker compose down", + "docker": "docker compose -f docker/docker-compose.yaml up -d && bash ./src/misc/.tmux.sh; docker compose -f docker/docker-compose.yaml down", + "docker:build": "npm run build:docker && npm run docker", "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.yaml --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", "lint": "npx eslint", "lint:fix": "npx eslint --fix", diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts index e9c04de3..0af379f6 100644 --- a/src/config/hostsystem.ts +++ b/src/config/hostsystem.ts @@ -1,4 +1,10 @@ -import { RUNNING_IN_DOCKER, VERSION } from "./variables"; +import { + RUNNING_IN_DOCKER, + VERSION, + HA_MASTER, + HA_UNSAFE, + TRUSTED_PROXYS, +} from "./variables"; import fs from "fs"; import logger from "../utils/logger"; import os from "os"; @@ -7,6 +13,8 @@ import { atomicWrite } from "../utils/atomicWrite"; const userConf = "./src/data/user.conf"; const inDocker: boolean = RUNNING_IN_DOCKER == "true"; const version: string = VERSION || "unknown"; +const masterNode: string = HA_MASTER === "true" ? "✓" : "✗"; +const unsafeSync: string = HA_UNSAFE === "true" ? "✓" : "✗"; function writeUserConf() { let previousConfig = null; @@ -54,9 +62,17 @@ function writeUserConf() { backendVersion: version, }; - logger.info( - `Starting at: ${startDetails.startedAt} - Version: ${startDetails.backendVersion} - Docker: ${installationDetails.inDocker} - Installed as: ${installationDetails.installedBy} - Platform: ${installationDetails.platform} - Arch: ${installationDetails.arch}`, - ); + logger.info("-----------------------------------------"); + logger.info(`Starting at : ${startDetails.startedAt}`); + logger.info(`Version : ${startDetails.backendVersion}`); + logger.info(`Docker : ${installationDetails.inDocker}`); + logger.info(`Running as : ${installationDetails.installedBy}`); + logger.info(`Platform : ${installationDetails.platform}`); + logger.info(`Arch : ${installationDetails.arch}`); + logger.info(`Master node : ${masterNode}`); + logger.info(`Unsafe sync : ${unsafeSync}`); + logger.info(`Proxies : ${TRUSTED_PROXYS}`); + logger.info("-----------------------------------------"); } export default writeUserConf; diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index 1b28b4f7..3e61b16f 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -172,7 +172,7 @@ async function synchronizeFilesWithNodes(): Promise { } const nodeUrl = - useUnsafeConnection == true + useUnsafeConnection === true ? `http://${node}/ha/sync` : `https://${node}/ha/sync`; @@ -259,7 +259,7 @@ async function ensureFileExists( const dirPath = path.dirname(filePath); await fs.promises.mkdir(dirPath, { recursive: true }); await fs.promises.writeFile(filePath, content, { flag: "w" }); - logger.info(`File created/updated: ${filePath}`); + logger.info(`File updated: ${filePath}`); } catch (error) { logger.error( `Error creating/updating file ${filePath}: ${(error as Error).message}`, diff --git a/src/misc/.tmux.sh b/src/misc/.tmux.sh new file mode 100644 index 00000000..a929a1a3 --- /dev/null +++ b/src/misc/.tmux.sh @@ -0,0 +1 @@ +[ -z "$TMUX" ] && tmux new-session -d -s docker 'docker compose -f docker/docker-compose.yaml logs -f master' \; rename-window 'master' \; new-window 'docker compose -f docker/docker-compose.yaml logs -f slave' \; rename-window 'slave' \; new-window 'docker compose -f docker/docker-compose.yaml logs -f test-socket-proxy' \; rename-window 'proxy' \; attach-session || echo 'Already inside a tmux session. Exiting.' diff --git a/src/server.ts b/src/server.ts index 6b680291..97e5337a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,4 @@ import express from "express"; -import logger from "./utils/logger"; import initializeApp from "./init"; import { startMasterNode } from "./controllers/highAvailability"; import writeUserConf from "./config/hostsystem"; @@ -7,10 +6,6 @@ import writeUserConf from "./config/hostsystem"; const app = express(); const PORT: number = 9876; -logger.info("Server starting up..."); -logger.info(`Server is running on http://localhost:${PORT}`); -logger.info(`Swagger docs available at http://localhost:${PORT}/api-docs\n`); - writeUserConf(); initializeApp(app); From 5df68298e495951f4e5b2d5bc6242227ad26c8df Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 2 Jan 2025 22:01:10 +0100 Subject: [PATCH 100/135] Fix: Run uglifyjs only on .js files --- src/misc/minifyDist.sh | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/misc/minifyDist.sh b/src/misc/minifyDist.sh index 0c256170..8a85b162 100755 --- a/src/misc/minifyDist.sh +++ b/src/misc/minifyDist.sh @@ -3,28 +3,28 @@ dist="$(pwd)/dist" run_script() { - npx uglifyjs --no-annotations --in-situ "$1" > /dev/null - echo "✔️ Minified : $(basename "$1")" + npx uglifyjs --no-annotations --in-situ "$1" > /dev/null + echo "✔️ Minified : $(basename "$1")" } if [ -d "$dist" ]; then - echo "::: Dist directory exists." + echo "::: Dist directory exists." else - echo "::: Dist does not exist... Running npx tsc" - npx tsc + echo "::: Dist does not exist... Running npx tsc" + npx tsc fi max_jobs=$(nproc) job_count=0 -for file in $(find "$dist" -type f); do - run_script "$file" & - ((job_count++)) +for file in $(find "$dist" -type f -name "*.js"); do + run_script "$file" & + ((job_count++)) - if ((job_count >= max_jobs)); then - wait - job_count=0 - fi + if ((job_count >= max_jobs)); then + wait + job_count=0 + fi done wait From 7fa7eeb7bce75625585bbf81e7fd39f818b8d295 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Thu, 2 Jan 2025 22:19:23 +0100 Subject: [PATCH 101/135] Err: Workflow wont run --- .github/workflows/validation.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 2226171e..10adc684 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -168,7 +168,7 @@ jobs: contents: read runs-on: ubuntu-24.04 if: github.ref_name == 'dev' - needs: [validation, test-building, Anchore, CodeQL] + needs: [test-building] steps: - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -193,12 +193,12 @@ jobs: flavor: | latest=false - - name: Build and push + - name: Build and Push Docker Images uses: docker/build-push-action@v6 with: - platforms: linux/amd64,linux/arm64, context: . file: docker/Dockerfile-dev + platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.metadata.outputs.tags }} labels: ${{ steps.metadata.outputs.labels }} From 9175d9a99219abd241b40b35aecaf8560e87d031 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 07:32:43 +0100 Subject: [PATCH 102/135] Fix: Update validation.yaml --- .github/workflows/validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 10adc684..ab37d212 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -197,7 +197,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/Dockerfile-dev + file: docker/Dockerfile-base platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.metadata.outputs.tags }} From f39f1bc50437fa10a64643b295118fab14058cc2 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 07:48:56 +0100 Subject: [PATCH 103/135] Fix: Update validation.yaml --- .github/workflows/validation.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index ab37d212..a6e496de 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -170,6 +170,9 @@ jobs: if: github.ref_name == 'dev' needs: [test-building] steps: + - name: Checkout Repository + uses: actions/checkout@v3 + - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 9bc2f651f54aa1b71b50ccb46ff1ff51863f8a9a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 08:11:13 +0100 Subject: [PATCH 104/135] Chore: Cleanup frontendConfiguration.json --- src/data/frontendConfiguration.json | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/data/frontendConfiguration.json b/src/data/frontendConfiguration.json index 884e0e20..fe51488c 100644 --- a/src/data/frontendConfiguration.json +++ b/src/data/frontendConfiguration.json @@ -1,16 +1 @@ -[ - { - "name": "test", - "tags": [ - "123", - "123", - "321" - ], - "link": "https://google.com", - "icon": "custom/test.png" - }, - { - "name": "test2", - "pinned": true - } -] \ No newline at end of file +[] From bb8a627aedbdf684d286d0375c66e150e4c27638 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 08:12:51 +0100 Subject: [PATCH 105/135] Fix: missing return in api.ts --- src/handlers/api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/api.ts b/src/handlers/api.ts index 37529688..6f62c056 100644 --- a/src/handlers/api.ts +++ b/src/handlers/api.ts @@ -126,7 +126,7 @@ class ApiHandler { try { const rawData = fs.readFileSync(configPath); const data = JSON.parse(rawData.toString()); - ResponseHandler.rawData(data, "Fetched frontend configuration"); + return ResponseHandler.rawData(data, "Fetched frontend configuration"); } catch (error: unknown) { const errorMsg = error instanceof Error ? error.message : String(error); return ResponseHandler.critical(errorMsg); From 987b550f4c2b3c2961836476b2d5d974d76d1f8f Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:15:15 +0100 Subject: [PATCH 106/135] Chore: Change to dev Dockerfile for workflow --- .github/workflows/validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index a6e496de..782a762c 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -200,7 +200,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/Dockerfile-base + file: docker/Dockerfile-dev platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.metadata.outputs.tags }} From 621d1426840fc525a0b3ed9df58a8a5a6092595b Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:23:39 +0100 Subject: [PATCH 107/135] Feat: Added docker scout --- .github/workflows/validation.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 782a762c..d15f1a4a 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -159,6 +159,23 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max + - name: Analyze for critical and high CVEs + id: docker-scout-cves + if: ${{ github.event_name != 'pull_request_target' }} + uses: docker/scout-action@v1 + with: + command: cves + image: ${{ steps.meta.outputs.tags }} + sarif-file: sarif.output.json + summary: true + + - name: Upload SARIF result + id: upload-sarif + if: ${{ github.event_name != 'pull_request_target' }} + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: sarif.output.json + build-dev: name: "Dev-build" permissions: From d932c98ca15a17f665ef0637618fac110a6d0bca Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:25:55 +0100 Subject: [PATCH 108/135] Feat: Update workflow logic --- .github/workflows/validation.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index d15f1a4a..d6836785 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -114,7 +114,7 @@ jobs: sarif_file: ./results.sarif test-building: - needs: [validation, Anchore, CodeQL] + needs: [validation] runs-on: ubuntu-24.04 name: "Test building" permissions: @@ -185,7 +185,7 @@ jobs: contents: read runs-on: ubuntu-24.04 if: github.ref_name == 'dev' - needs: [test-building] + needs: [test-building, Anchore, CodeQL] steps: - name: Checkout Repository uses: actions/checkout@v3 From c87ba873a7e121643694e2d693e90d3a8987daac Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:31:23 +0100 Subject: [PATCH 109/135] Fix: Permissions --- .github/workflows/validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index d6836785..6016534a 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -121,7 +121,7 @@ jobs: security-events: write packages: read actions: read - contents: read + contents: write steps: - name: Checkout repository uses: actions/checkout@v4 From 9cb349059663cd4a210cbe13d1adadfa236ea656 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:36:34 +0100 Subject: [PATCH 110/135] Fix: Workflow adjustment => drop docker scout :/ --- .github/workflows/validation.yaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 6016534a..7414ee52 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -159,23 +159,6 @@ jobs: cache-from: type=gha cache-to: type=gha,mode=max - - name: Analyze for critical and high CVEs - id: docker-scout-cves - if: ${{ github.event_name != 'pull_request_target' }} - uses: docker/scout-action@v1 - with: - command: cves - image: ${{ steps.meta.outputs.tags }} - sarif-file: sarif.output.json - summary: true - - - name: Upload SARIF result - id: upload-sarif - if: ${{ github.event_name != 'pull_request_target' }} - uses: github/codeql-action/upload-sarif@v2 - with: - sarif_file: sarif.output.json - build-dev: name: "Dev-build" permissions: From 7b09298399a903590210acb1b1abb4b5168b6ae2 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 09:47:03 +0100 Subject: [PATCH 111/135] Fix: Remove unnecessary matrix --- .github/workflows/validation.yaml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 7414ee52..7392c838 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -49,13 +49,6 @@ jobs: actions: read contents: read - strategy: - fail-fast: false - matrix: - include: - - language: javascript-typescript - build-mode: none - steps: - name: Checkout repository uses: actions/checkout@v4 @@ -64,20 +57,9 @@ jobs: uses: github/codeql-action/init@v3 with: languages: javascript-typescript - build-mode: ${{ matrix.build-mode }} + build-mode: none queries: security-extended - - name: Check build mode - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: From 204d4bf037bcd8225c8cbf22117d8faf59bdfcc0 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 14:27:59 +0100 Subject: [PATCH 112/135] Chore: Pre release workflow --- .github/workflows/validation.yaml | 55 ++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 7392c838..1763ee96 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -1,6 +1,10 @@ name: "Run all tests" -on: [push] +on: + push: + release: + types: + - published jobs: validation: @@ -189,3 +193,52 @@ jobs: labels: ${{ steps.metadata.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max + + build-pre-release: + name: "Pre-Release-build" + permissions: + security-events: read + packages: write + actions: read + contents: read + runs-on: ubuntu-24.04 + if: "!github.event.release.prerelease" + needs: [test-building, Anchore, CodeQL] + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Github Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ github.token }} + + - name: Generate Docker tags + uses: docker/metadata-action@v5 + id: metadata + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=raw,enable=true,priority=200,prefix=,suffix=,value=pre + flavor: | + latest=false + + - name: Build and Push Docker Images + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile-dev + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.metadata.outputs.tags }} + labels: ${{ steps.metadata.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From b27369fdc2697b04ff9b847d1da957e08f3e825a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Fri, 3 Jan 2025 14:34:57 +0100 Subject: [PATCH 113/135] Fix: removed negation from if in workflow --- .github/workflows/validation.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 1763ee96..7040e940 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -202,7 +202,7 @@ jobs: actions: read contents: read runs-on: ubuntu-24.04 - if: "!github.event.release.prerelease" + if: "github.event.release.prerelease" needs: [test-building, Anchore, CodeQL] steps: - name: Checkout Repository From fe37f2daa0ef3068348788af4aa874824399d693 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 5 Jan 2025 14:16:25 +0100 Subject: [PATCH 114/135] Feat: Added new image => See next commit --- .github/DockStat-dark.png | Bin 0 -> 82847 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/DockStat-dark.png diff --git a/.github/DockStat-dark.png b/.github/DockStat-dark.png new file mode 100644 index 0000000000000000000000000000000000000000..00ac779a6dcb303ce5c690aa079dedc175e5c8f3 GIT binary patch literal 82847 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

TLgl9u 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 AX#fBK literal 0 HcmV?d00001 From a82d021d68ee30fe949dc35e894f20932223fe8e Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 29 Jan 2025 20:19:01 +0100 Subject: [PATCH 115/135] Fix: Add checkout --- .github/workflows/build-image.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 41f18cb8..5e0db4ce 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -12,6 +12,9 @@ jobs: build-release: runs-on: ubuntu-24.04 steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 0f3ed69ac6d319fdbcbcb3d841cf4b637dcbc57b Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 9 Feb 2025 14:37:41 +0100 Subject: [PATCH 116/135] Feat: Add WebSockets, graph generation service and HA (#31) * Chore: new testing workflows (dropping playwright) * Chore: updating github workflow * Fix: Fixing some minor things * Chore: Updated to ES2020 syntax and AMD module * Feat: startServer function to start the server with a different port * Fix: Adjusting testing files based on workflow restrictions * Fix: Adjusting testing files based on workflow restrictions * Chore: Updating swagger (wrong branch bruh) * Docs: Update swagger documentation (#26) * Chore: Updated swagger * Fix: Typo * Fix: Fixing dockerfiles for prod/dev environment * Feat: Add `/graph` and `/graph/image` endpoints (#27) * Feat: Server side HTML generation => Client side rendering * Fix: This _might_ fix the workflow * Fix: Remove unused function * Fix: Please make it stop * Fix: Setting up python before hand * Fix: Remove unused dep * Fix: Using node20 instead of latest * Fix: Works on my end... * Feat: Master Nodes * Feat: Icon for master node (needs testing) * Fix: Adjusting function (needs testing) * Fix: Adjusting function (needs testing) * Fix: Removed some graph rendering features (will be back but better) * Feat: render html file as png using puppeteer ToFix: svgs dont render * Fix: Hell yeah we got image creation! * Fix: Adjusted routes since they need an absolute path * Fix: Remove unused dependencies * Fix: Exclude CWE-200 from CodeQl * Feat: Respomse examples in swagger Fix: Fixing some catch blocks * Fix: Adjusting catches * Fix: Adjusted catch to typing * Feat: Stack creation * Feat: Stack creation + starting and stopping * Fix: Project root instead of path Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Logging adustment * Fix: Allow undescores and dashes in stack name Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Propagate error * Fix: Inline variable that is immediately returned Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: move some things around * Fix: Minor adjustments * Feat: Get a stack's docker-compose * Feat: automatic Stack environmental file management * Fix: sample-varaible.json adjustment * Fix: Potential fix for code scanning alert no. 102: Log injection Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Fix: fix for code scanning alert no. 94: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Fix: fix for code scanning alert no. 92: Uncontrolled data used in path expression Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * FiX: fix for code scanning alert no. 106: Log injection Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Fix: Logger vulnerability and CI graph generation * Feat: change logger verbosity and spelling fix * Feat: ToDo comments to GH issue * Fix: Add checkout * Fix: May fix the ToDo workflow * Fix: Remove todo * Fix: Re-Add commit * Fix: Remove TODO * Fix: Re-add TODO * Fix: Where tf did my package lock go :sob: * CI/CD: Remove ToDo * CI/CD: Add ToDo * CI/CD: Fix command * CI/CD: Add checkout * Fix: CPU value was a percentage the whole time? * Feat: Websocket endpoints for logs and container metrics * Fix: Make linter happy * Fix: Fix import * Fix: Fix tsc build * Jest: Fix tests * Jest: Fix Tests * Fix: Typo in src/config/swagger.yaml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Typo in src/config/swagger.yaml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Tyypo in src/config/swagger.yaml Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * (code-quality): Inline variable that is immediately returned Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * (code-quality): Prefer object destructuring when accessing and using properties. Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * (code-quality): Prefer object destructuring when accessing and using properties. Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * (code-quality): Prefer object destructuring when accessing and using properties. Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * Fix: Update extractHostData.ts * Update TODO.md --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: ItsNik --- .dockerignore | 7 +- .github/workflows/build-image.yaml | 24 +- .github/workflows/validation.yaml | 67 +- .gitignore | 3 + CREDITS.md | 106 +- README.md | 1 + TODO.md | 5 +- __tests__/auth.spec.ts | 38 + __tests__/config.spec.ts | 49 + __tests__/database.spec.ts | 35 + __tests__/frontend.spec.ts | 125 + __tests__/getters.spec.ts | 99 + __tests__/util/previousResponse.ts | 23 + docker/Dockerfile-base | 19 +- docker/Dockerfile-dev | 19 +- docker/docker-compose.dev.yaml | 40 + docker/docker-compose.yaml | 1 + environment.d.ts | 38 +- eslint.config.mjs | 2 +- nodemon.json | 3 +- package-lock.json | 9462 ++++++++++++++--- package.json | 74 +- playwright.config.ts | 37 - src/config/db.ts | 2 +- src/config/hostsystem.ts | 21 +- src/config/initFiles.ts | 1 + src/config/stacks.ts | 260 + src/config/swagger.yaml | 2095 ++++ src/config/swaggerConfig.ts | 59 +- src/config/swaggerTheme.ts | 6 + src/config/variables.ts | 4 +- src/controllers/containerController.ts | 2 +- src/controllers/fetchData.ts | 7 +- src/controllers/frontendConfiguration.ts | 46 +- src/controllers/highAvailability.ts | 38 +- src/controllers/proxy.ts | 4 +- src/controllers/scheduler.ts | 8 +- src/data/frontendConfiguration.json | 2 +- src/handlers/api.ts | 8 +- src/handlers/conf.ts | 9 +- src/handlers/data.ts | 30 + src/handlers/graph.ts | 257 + src/handlers/notification.ts | 5 +- src/handlers/response.ts | 2 +- src/handlers/stack.ts | 162 + src/init.ts | 36 +- src/middleware/authMiddleware.ts | 2 +- src/misc/createEnvDev.sh | 14 +- src/misc/createEnvFile.sh | 14 +- .../dependencyGraphs/createDependencyGraph.sh | 2 +- src/misc/dependencyGraphs/mermaid-all.txt | 166 +- src/misc/dependencyGraphs/mermaid-graph.txt | 15 + src/misc/entrypoint.sh | 8 +- src/misc/minifyDist.sh | 2 +- src/routes/auth/routes.ts | 42 - src/routes/data/routes.ts | 134 - src/routes/frontendController/routes.ts | 441 - src/routes/getter/routes.ts | 273 - src/routes/graphs/routes.ts | 31 + src/routes/highavailability/routes.ts | 30 - src/routes/notifications/routes.ts | 109 - src/routes/setter/routes.ts | 73 +- src/routes/stack/routes.ts | 35 + src/sample-variable.json | 6 +- src/server.ts | 18 +- src/typings/dockerCompose.ts | 92 + src/typings/dockerStackEnv.ts | 10 + src/typings/ha.ts | 2 +- src/typings/stackConfig.ts | 5 + src/utils/assets/api-icon.svg | 1 + src/utils/assets/container-icon.svg | 1 + src/utils/assets/server-icon.svg | 1 + src/utils/atomicWrite.ts | 2 +- src/utils/connectionChecker.ts | 4 +- src/utils/containerService.ts | 223 +- src/utils/dockerClient.ts | 14 +- src/utils/extractHostData.ts | 73 +- src/utils/logger.ts | 10 +- src/utils/notifications/_template.ts | 9 +- src/utils/notifications/email.ts | 3 +- src/utils/startServer.ts | 18 + src/utils/swaggerDocs.ts | 9 +- src/utils/webSocket.ts | 113 + tests/main.spec.ts | 131 - tsconfig.json | 2 +- 85 files changed, 12086 insertions(+), 3393 deletions(-) create mode 100644 __tests__/auth.spec.ts create mode 100644 __tests__/config.spec.ts create mode 100644 __tests__/database.spec.ts create mode 100644 __tests__/frontend.spec.ts create mode 100644 __tests__/getters.spec.ts create mode 100644 __tests__/util/previousResponse.ts create mode 100644 docker/docker-compose.dev.yaml delete mode 100644 playwright.config.ts create mode 100644 src/config/stacks.ts create mode 100644 src/config/swagger.yaml create mode 100644 src/config/swaggerTheme.ts create mode 100644 src/handlers/graph.ts create mode 100644 src/handlers/stack.ts create mode 100644 src/misc/dependencyGraphs/mermaid-graph.txt create mode 100644 src/routes/graphs/routes.ts create mode 100644 src/routes/stack/routes.ts create mode 100644 src/typings/dockerCompose.ts create mode 100644 src/typings/dockerStackEnv.ts create mode 100644 src/typings/stackConfig.ts create mode 100644 src/utils/assets/api-icon.svg create mode 100644 src/utils/assets/container-icon.svg create mode 100644 src/utils/assets/server-icon.svg create mode 100644 src/utils/startServer.ts create mode 100644 src/utils/webSocket.ts delete mode 100644 tests/main.spec.ts diff --git a/.dockerignore b/.dockerignore index 2d993096..6381947a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,11 +1,12 @@ # custom paths: src/data/* -*.md -*.txt -docker +.tmp +docker/master +docker/slave .test* # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### +*-audit.json # Logs logs *.log diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index 5e0db4ce..bbb4875d 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -1,4 +1,4 @@ -name: "Build dockstatapi:latest" +name: "Build and Push Docker Image" on: release: @@ -21,22 +21,34 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Github Container Registry + - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ github.token }} - - name: Generate Docker tags + - name: Extract version and create tag + id: get-tag + run: | + # Remove 'v' prefix from release tag if present + VERSION="${GITHUB_REF#refs/tags/v}" + # Check if pre-release and append '-pre' + if ${{ github.event.release.prerelease }}; then + TAG="$VERSION-pre" + else + TAG="$VERSION" + fi + echo "tag=$TAG" >> $GITHUB_OUTPUT + + - name: Generate Docker metadata uses: docker/metadata-action@v5 id: metadata with: images: ghcr.io/${{ github.repository }} tags: | - type=sha,format=long,prefix= - flavor: | - latest=true + type=raw,value=${{ steps.get-tag.outputs.tag }} + type=raw,value=latest,enable=${{ !github.event.release.prerelease }} - name: Build and push uses: docker/build-push-action@v6 diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 7040e940..7e2b685c 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -26,7 +26,7 @@ jobs: cache: npm - name: Install dependencies - run: npm ci --ignore-scripts + run: npm ci - name: Create varaibles.json run: npm run local-env-file @@ -43,8 +43,43 @@ jobs: - name: Audit packages run: npm audit --audit-level=high - CodeQL: + - name: Jests + run: npm run test:silent + + ToDo: needs: validation + runs-on: ubuntu-20.04 + name: "ToDo comment to issue" + permissions: + contents: write + issues: write + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: "TODO to Issue" + uses: "alstr/todo-to-issue-action@v5" + with: + INSERT_ISSUE_URLS: "true" + + - name: Set Git user + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Commit and Push Changes + run: | + git add -A + if [[ `git status --porcelain` ]]; then + git commit -m "Automatically added GitHub issue links to TODOs" + git push + else + echo "No changes to commit" + fi + + CodeQL: + needs: [ToDo] runs-on: ubuntu-24.04 name: "Analyze TypeScript" permissions: @@ -57,12 +92,21 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: javascript-typescript build-mode: none queries: security-extended + config: | + query-filter: + - exclude: + tags: /cwe-200/ - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 @@ -70,7 +114,7 @@ jobs: category: "/language:javascript-typescript" Anchore: - needs: validation + needs: [ToDo] runs-on: ubuntu-24.04 name: "Anchore" permissions: @@ -82,6 +126,11 @@ jobs: - name: Set up Grype installation path run: echo "$HOME/bin" >> $GITHUB_PATH + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Download Grype run: | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin @@ -100,7 +149,7 @@ jobs: sarif_file: ./results.sarif test-building: - needs: [validation] + needs: [ToDo] runs-on: ubuntu-24.04 name: "Test building" permissions: @@ -112,6 +161,11 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -159,6 +213,11 @@ jobs: - name: Checkout Repository uses: actions/checkout@v3 + - name: Setup python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.gitignore b/.gitignore index dc93b889..9e264ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,12 @@ # custom paths: src/data/* +src/data/frontendConfiguration.json +.tmp docker/master docker/slave .test* +stacks # Created by https://www.toptal.com/developers/gitignore/api/node ### Node ### *-audit.json diff --git a/CREDITS.md b/CREDITS.md index 050b430b..50b66abb 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -8,35 +8,81 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | ----------------- | -------------------------------------------- | -------------------- | | spdx-ranges@2.1.1 | https://github.com/kemitchell/spdx-ranges.js | The Linux Foundation | +### License: Apache 2.0 + +| Name | Repository | Publisher | +| ---------------------- | ------------------------------------------ | --------- | +| qrcode-terminal@0.12.0 | https://github.com/gtanner/qrcode-terminal | N/A | + ### License: Apache-2.0 -| Name | Repository | Publisher | -| ------------------------------------ | ------------------------------------------------------------- | --------------------- | -| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | -| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | -| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @playwright/test@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | -| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | -| doctrine@3.0.0 | https://github.com/eslint/doctrine | N/A | -| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | -| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | -| playwright-core@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| playwright@1.49.1 | https://github.com/microsoft/playwright | Microsoft Corporation | -| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | -| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | -| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | +| Name | Repository | Publisher | +| ------------------------------------ | ------------------------------------------------------------------------ | -------------------- | +| @ampproject/remapping@2.3.0 | https://github.com/ampproject/remapping | Justin Ridgewell | +| @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | +| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | +| @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | +| @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | +| @puppeteer/browsers@2.7.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/browsers | The Chromium Authors | +| @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | +| @sigstore/bundle@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| @sigstore/core@2.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| @sigstore/protobuf-specs@0.3.2 | https://github.com/sigstore/protobuf-specs | bdehamer@github.com | +| @sigstore/sign@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| @sigstore/tuf@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| @sigstore/verify@2.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| b4a@1.6.7 | https://github.com/holepunchto/b4a | Holepunch | +| bare-events@2.5.4 | https://github.com/holepunchto/bare-events | Holepunch | +| bare-fs@2.3.5 | https://github.com/holepunchto/bare-fs | Holepunch | +| bare-os@2.4.4 | https://github.com/holepunchto/bare-os | Holepunch | +| bare-path@2.1.3 | https://github.com/holepunchto/bare-path | Holepunch | +| bare-stream@2.6.1 | https://github.com/holepunchto/bare-stream | Holepunch | +| bser@2.1.1 | https://github.com/facebook/watchman | Wez Furlong | +| chromium-bidi@0.11.0 | https://github.com/GoogleChromeLabs/chromium-bidi | The Chromium Authors | +| chromium-bidi@0.12.0 | https://github.com/GoogleChromeLabs/chromium-bidi | The Chromium Authors | +| detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | +| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| ejs@3.1.10 | https://github.com/mde/ejs | Matthew Eernisse | +| eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | +| eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | +| exponential-backoff@3.1.1 | https://github.com/coveo/exponential-backoff | Sami Sayegh | +| fb-watchman@2.0.2 | https://github.com/facebook/watchman | Wez Furlong | +| filelist@1.0.4 | https://github.com/mde/filelist | Matthew Eernisse | +| human-signals@2.1.0 | https://github.com/ehmicky/human-signals | ehmicky | +| jake@10.9.2 | https://github.com/jakejs/jake | Matthew Eernisse | +| puppeteer-core@24.0.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core | The Chromium Authors | +| puppeteer@24.0.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer | The Chromium Authors | +| sigstore@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | +| spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | +| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| text-decoder@1.2.3 | https://github.com/holepunchto/text-decoder | Holepunch | +| tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | +| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | +| walker@1.0.8 | https://github.com/daaku/nodejs-walker | Naitik Shah | + +### License: Artistic-2.0 + +| Name | Repository | Publisher | +| ---------- | -------------------------- | ----------- | +| npm@11.0.0 | https://github.com/npm/cli | GitHub Inc. | + +### License: BlueOak-1.0.0 + +| Name | Repository | Publisher | +| ---------------------------- | ------------------------------------------------ | ------------------ | +| chownr@3.0.0 | https://github.com/isaacs/chownr | Isaac Z. Schlueter | +| jackspeak@3.4.3 | https://github.com/isaacs/jackspeak | Isaac Z. Schlueter | +| package-json-from-dist@1.0.1 | https://github.com/isaacs/package-json-from-dist | Isaac Z. Schlueter | +| path-scurry@1.11.1 | https://github.com/isaacs/path-scurry | Isaac Z. Schlueter | +| yallist@5.0.0 | https://github.com/isaacs/yallist | Isaac Z. Schlueter | ### License: CC-BY-3.0 @@ -44,6 +90,12 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | --------------------- | -------------------------------------------------- | -------------------- | | spdx-exceptions@2.5.0 | https://github.com/kemitchell/spdx-exceptions.json | The Linux Foundation | +### License: CC-BY-4.0 + +| Name | Repository | Publisher | +| ------------------------- | -------------------------------------------- | ---------- | +| caniuse-lite@1.0.30001690 | https://github.com/browserslist/caniuse-lite | Ben Briggs | + ### License: Python-2.0 | Name | Repository | Publisher | diff --git a/README.md b/README.md index 8fc800a8..25778667 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ With this new release a couple of extra features (compared to v1) are going to b - Advanced security through middlewares: rate-limiting and authentication - Multi Arch Docker builds through docker buildx - High Availability using single master and unlimited worker nodes! +- Dynamically created Graphs # 🔗 DockStatAPI v2 Documentation diff --git a/TODO.md b/TODO.md index 7ac3d438..44a128d3 100644 --- a/TODO.md +++ b/TODO.md @@ -7,11 +7,12 @@ - [x] Structure code differently - [x] Write new README and make the docs better - [x] Update more files to correct TS syntax => remove "any" -- [ ] Websockets +- [X] Websockets - [x] Better /api/status endpoint with connection status of each host - [x] Update notification service - [x] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) - [ ] Better project structure - [x] Update logging => Better errors - [x] Update json responses -- [ ] Swagger update +- [X] Swagger update +- [ ] Edge case testing diff --git a/__tests__/auth.spec.ts b/__tests__/auth.spec.ts new file mode 100644 index 00000000..bcf0eb21 --- /dev/null +++ b/__tests__/auth.spec.ts @@ -0,0 +1,38 @@ +export const testPass = "123456789"; +import { Server } from 'http'; +import supertest from "supertest"; +import { startServer } from "../src/utils/startServer"; +import app from "../src/server"; + +const port = 13001; +const server = new Server(app); + +startServer(app, server, port); + +const request = supertest(`http://localhost:${port}`); + +describe("Authentication", () => { + it("Enable Authentication", async () => { + const res = await request.post(`/auth/enable?password=${testPass}`); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + expect(res.body).toHaveProperty( + "message", + "Authentication enabled successfully", + ); + }); + + it("Test no password", async () => { + const res = await request.get("/api/status"); + expect(res.status).toEqual(403); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("Disable authentication", async () => { + const res = await request + .post(`/auth/disable?password=${testPass}`) + .set("x-password", testPass); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); +}); \ No newline at end of file diff --git a/__tests__/config.spec.ts b/__tests__/config.spec.ts new file mode 100644 index 00000000..d6356004 --- /dev/null +++ b/__tests__/config.spec.ts @@ -0,0 +1,49 @@ +import supertest from "supertest"; +import { startServer } from "../src/utils/startServer"; +import app from "../src/server"; +import { Server } from 'http'; + +const port = 13002; +const server = new Server(app); + +startServer(app, server, port); + +const request = supertest(`http://localhost:${port}`); + +const mockServerName: string = "mockstatapi"; +const mockServerIP: string = "127.0.0.1"; +const mockServerPort: number = 2375; + +describe("Config endpoints", () => { + it("Add an host", async () => { + let res = await request.put( + `/conf/addHost?name=${mockServerName}&url=${mockServerIP}&port=${mockServerPort}`, + ); + expect(res.status).toEqual(200); + + res = await request.get("/api/hosts"); + expect(res.status).toEqual(200); + expect(res.body).toContain("mockstatapi"); + }); + + it("Adjust scheduler", async () => { + let res = await request.put("/conf/scheduler?interval=10m"); + expect(res.status).toEqual(200); + + res = await request.get("/api/current-schedule"); + expect(res.status).toEqual(200); + + // Reset to standart 5m + res = await request.put("/conf/scheduler?interval=5m"); + expect(res.status).toEqual(200); + }); + + it("Remove Host from config", async () => { + let res = await request.delete(`/conf/removeHost?hostName=mockstatapi`); + expect(res.status).toEqual(200); + + res = await request.get("/api/hosts"); + expect(res.status).toEqual(200); + expect(res.body).not.toHaveProperty("mockstatapi"); + }); +}); diff --git a/__tests__/database.spec.ts b/__tests__/database.spec.ts new file mode 100644 index 00000000..c0c46c1b --- /dev/null +++ b/__tests__/database.spec.ts @@ -0,0 +1,35 @@ +import supertest from "supertest"; +import { startServer } from "../src/utils/startServer"; +import app from "../src/server"; +import { Server } from 'http'; + +const port = 13003; +const server = new Server(app); + +startServer(app, server, port); + +const request = supertest(`http://localhost:${port}`); + +describe("Database", () => { + it("Get latest database entry", async () => { + const res = await request.get("/data/latest"); + expect(res.status).toEqual(200); + }); + + it("Get all database entries", async () => { + const res = await request.get("/data/all"); + expect(res.status).toEqual(200); + }); + + it("Clear database", async () => { + let res = await request.delete("/data/clear"); + expect(res.status).toEqual(200); + + res = await request.get("/data/latest"); + expect(res.status).toEqual(404); + expect(res.body).toHaveProperty( + "message", + "No data available for /data/latest", + ); + }); +}); diff --git a/__tests__/frontend.spec.ts b/__tests__/frontend.spec.ts new file mode 100644 index 00000000..753b98da --- /dev/null +++ b/__tests__/frontend.spec.ts @@ -0,0 +1,125 @@ +import supertest from "supertest"; +import { startServer } from "../src/utils/startServer"; +import app from "../src/server"; +import { Server } from 'http'; + +const port = 13004; +const server = new Server(app); + +startServer(app, server, port); + +const request = supertest(`http://localhost:${port}`); + +const sec: number = 1000; + +const mockContainer: string = "dockstatapi"; +const mockLink: string = "https://github.com/its4nik/dockstatapi"; +const mockIcon: string = "dockstatapi.png"; +const mockTag1: string = "backend"; +const mockTag2: string = "local"; + +const verifiedResponse = [ + { + name: "dockstatapi", + tags: ["backend", "local"], + pinned: true, + link: "https://github.com/its4nik/dockstatapi", + icon: "dockstatapi.png", + hidden: true, + }, +]; + + + +describe("Test frontend specific configurations", () => { + it( + "Setup the configuration file", + async () => { + // Hide container + let res = await request.delete(`/frontend/hide/${mockContainer}`); + + expect(res.status).toEqual(200); + + // Add Tag(s) + res = await request.post(`/frontend/tag/${mockContainer}/${mockTag1}`); + + expect(res.status).toEqual(200); + res = await request.post(`/frontend/tag/${mockContainer}/${mockTag2}`); + + expect(res.status).toEqual(200); + + // Pin container + res = await request.post(`/frontend/pin/${mockContainer}`); + + expect(res.status).toEqual(200); + + // Add link + res = await request.post( + `/frontend/add-link/${mockContainer}/${encodeURIComponent(mockLink)}`, + ); + + expect(res.status).toEqual(200); + + // Add icon + res = await request.post( + `/frontend/add-icon/${mockContainer}/${mockIcon}/false`, + ); + + expect(res.status).toEqual(200); + }, + 60 * sec, + ); + + it("Verify the configuration", async () => { + const res = await request.get("/api/frontend-config"); + + expect(res.status).toEqual(200); + expect(res.body).toEqual(verifiedResponse); + }); + + it( + "Reset configuration", + async () => { + // Show container + let res = await request.post(`/frontend/show/${mockContainer}`); + + expect(res.status).toEqual(200); + + // Remove tag(s) + res = await request.delete( + `/frontend/remove-tag/${mockContainer}/${mockTag1}`, + ); + + expect(res.status).toEqual(200); + + res = await request.delete( + `/frontend/remove-tag/${mockContainer}/${mockTag2}`, + ); + + expect(res.status).toEqual(200); + + // Unpin + res = await request.delete(`/frontend/unpin/${mockContainer}`); + + expect(res.status).toEqual(200); + + // Remove link + res = await request.delete(`/frontend/remove-link/${mockContainer}`); + + expect(res.status).toEqual(200); + + // Remove icon + res = await request.delete(`/frontend/remove-icon/${mockContainer}`); + + expect(res.status).toEqual(200); + }, + 60 * sec, + ); + + it("Verify the reset configuration", async () => { + const res = await request.get("/api/frontend-config"); + + expect(res.status).toEqual(200); + expect(res.body).toEqual([]); + }); +}); diff --git a/__tests__/getters.spec.ts b/__tests__/getters.spec.ts new file mode 100644 index 00000000..3ba5950b --- /dev/null +++ b/__tests__/getters.spec.ts @@ -0,0 +1,99 @@ +import { createPreviousResponse } from "./util/previousResponse"; +import supertest from "supertest"; +import { startServer } from "../src/utils/startServer"; +import app from "../src/server"; +import { Server } from 'http'; + +const port = 13005; +const server = new Server(app); + +startServer(app, server, port); + +const request = supertest(`http://localhost:${port}`); +const PreviousResponse = createPreviousResponse(); + +describe("Get endpoints", () => { + it("GET /api/hosts", async () => { + const res = await request.get("/api/hosts"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + + const hosts: string[] = res.body; + + if (hosts.length >= 1) { + expect(Array.isArray(hosts)).toBe(true); + expect(hosts.length).toBeGreaterThan(0); + expect(typeof hosts[0]).toBe("string"); + PreviousResponse.set(hosts[0]); + } + }); + + it("GET /api/host/:host/stats", async () => { + const host = PreviousResponse.get(); + + if (!host) { + console.log("No hosts found, skipping /api/host/:host/stats test"); + return; + } + + const res = await request.get(`/api/host/${host}/stats`); + + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("GET /api/system", async () => { + const res = await request.get("/api/system"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("GET /api/status", async () => { + const res = await request.get("/api/status"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + expect(res.body).toHaveProperty("ApiReachable", true); + }); + + it("GET /api/containers", async () => { + const res = await request.get("/api/containers"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("GET /api/config", async () => { + const res = await request.get("/api/config"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + expect(res.body).toHaveProperty("hosts"); + }); + + it("GET /api/current-schedule", async () => { + const res = await request.get("/api/current-schedule"); + + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + expect(res.body).toHaveProperty("interval"); + }); + + it("GET /api/frontend-config", async () => { + const res = await request.get("/api/frontend-config"); + + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("GET /ha/config", async () => { + const res = await request.get("/ha/config"); + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + }); + + it("GET /notification-service/get-template", async () => { + const res = await request.get("/notification-service/get-template"); + + expect(res.status).toEqual(200); + expect(res.type).toEqual(expect.stringContaining("json")); + expect(res.body).toHaveProperty("text"); + }); +}); diff --git a/__tests__/util/previousResponse.ts b/__tests__/util/previousResponse.ts new file mode 100644 index 00000000..774a862a --- /dev/null +++ b/__tests__/util/previousResponse.ts @@ -0,0 +1,23 @@ +let response: string = ""; + +class PreviousResponse { + set(body: unknown): void { + try { + response = JSON.stringify(body).replace(/[" ]/g, ""); + } catch (error: unknown) { + console.error("Error in setting response:", error); + throw new Error("Failed to set response"); + } + } + + get(): string { + try { + return response; + } catch (error: unknown) { + console.error("Error in getting response:", error); + throw new Error("Failed to get response"); + } + } +} + +export const createPreviousResponse = () => new PreviousResponse(); diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index 1f9bf30d..76cec4c9 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:alpine AS builder +FROM node:20-alpine AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" @@ -19,16 +19,14 @@ RUN apk add --no-cache bash COPY tsconfig.json environment.d.ts package*.json ./ -RUN export npm_config_cache=$(mktemp -d) && \ - npm install --production=false && \ - rm -rf $npm_config_cache /tmp/*.log +RUN npm install --production=false COPY ./src ./src RUN mv ./src/sample-variable.json ./src/data/variables.json RUN npm run build:mini # Stage 2: Production stage -FROM node:alpine AS production +FROM node:20-alpine AS production WORKDIR /api @@ -40,10 +38,10 @@ HEALTHCHECK --interval=5m --timeout=3s \ COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets -RUN export npm_config_cache=$(mktemp -d) && \ - npm install --omit=dev && \ - rm -rf $npm_config_cache /tmp/*.log +RUN npm install --omit=dev COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh @@ -51,9 +49,10 @@ RUN chmod +x /api/*.sh EXPOSE 9876 -RUN chmod -R 777 /api/src/data /api && \ +RUN mkdir -p /api/src/data && \ + chmod -R 777 /api/src/data /api && \ chown -R dockstatapi:dockstatapi /api STOPSIGNAL 130 USER dockstatapi -ENTRYPOINT [ "bash", "./entrypoint.sh" ] +ENTRYPOINT [ "bash", "./entrypoint.sh", "--prod" ] diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index 58b9f43d..43a42402 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:alpine AS builder +FROM node:20-alpine AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" @@ -19,16 +19,14 @@ RUN apk add --no-cache bash COPY tsconfig.json environment.d.ts package*.json ./ -RUN export npm_config_cache=$(mktemp -d) && \ - npm install --production=false && \ - rm -rf $npm_config_cache /tmp/*.log +RUN npm install --production=false COPY ./src ./src RUN mv ./src/sample-variable.json ./src/data/variables.json RUN npm run build # Stage 2: Production stage -FROM node:alpine AS production +FROM node:20-alpine AS production WORKDIR /api @@ -40,10 +38,10 @@ HEALTHCHECK --interval=5m --timeout=3s \ COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml +COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets -RUN export npm_config_cache=$(mktemp -d) && \ - npm install --omit=dev && \ - rm -rf $npm_config_cache /tmp/*.log +RUN npm install COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh @@ -51,9 +49,10 @@ RUN chmod +x /api/*.sh EXPOSE 9876 -RUN chmod -R 777 /api/src/data /api && \ +RUN mkdir -p /api/src/data && \ + chmod -R 777 /api/src/data /api && \ chown -R dockstatapi:dockstatapi /api STOPSIGNAL 130 USER dockstatapi -ENTRYPOINT [ "bash", "./entrypoint.sh" ] +ENTRYPOINT [ "bash", "./entrypoint.sh", "--dev" ] diff --git a/docker/docker-compose.dev.yaml b/docker/docker-compose.dev.yaml new file mode 100644 index 00000000..7bc3773f --- /dev/null +++ b/docker/docker-compose.dev.yaml @@ -0,0 +1,40 @@ +services: + test-socket-proxy: + image: lscr.io/linuxserver/socket-proxy:latest + container_name: test-socket-proxy + environment: + - ALLOW_START=1 #optional + - ALLOW_STOP=1 #optional + - ALLOW_RESTARTS=1 #optional + - AUTH=0 #optional + - BUILD=0 #optional + - COMMIT=0 #optional + - CONFIGS=0 #optional + - CONTAINERS=1 #optional + - DISABLE_IPV6=0 #optional + - DISTRIBUTION=0 #optional + - EVENTS=1 #optional + - EXEC=0 #optional + - IMAGES=0 #optional + - INFO=1 #optional + - NETWORKS=1 #optional + - NODES=1 #optional + - PING=1 #optional + - POST=0 #optional + - PLUGINS=0 #optional + - SECRETS=0 #optional + - SERVICES=0 #optional + - SESSION=0 #optional + - SWARM=0 #optional + - SYSTEM=0 #optional + - TASKS=0 #optional + - VERSION=1 #optional + - VOLUMES=0 #optional + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + restart: unless-stopped + read_only: true + tmpfs: + - /run + ports: + - 2375:2375 \ No newline at end of file diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 225c5de2..436d8a21 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -79,3 +79,4 @@ services: - /run networks: - shared-network + diff --git a/environment.d.ts b/environment.d.ts index 803ae43c..df2595f5 100644 --- a/environment.d.ts +++ b/environment.d.ts @@ -2,41 +2,9 @@ declare global { namespace NodeJS { interface ProcessEnv { // Node specific: - NODE_ENV: "development" | "production"; - TRUSTED_PROXYS: string | undefined; - - // User.conf - RUNNING_IN_DOCKER: string | undefined; - VERSION: string | undefined; - - // High Availability - HA_MASTER: string | undefined; //bool - HA_MASTER_IP: string | undefined; - HA_NODE: string | undefined; //ip list with port seperated by "," like: "10.0.0.4:5012,10.0.0.5:9876" - HA_UNSAFE: string | undefined; - - // Notification services: - DISCORD_WEBHOOK_URL: string | undefined; - - EMAIL_SENDER: string | undefined; - EMAIL_RECIPIENT: string | undefined; - EMAIL_PASSWORD: string | undefined; - EMAIL_SERVICE: string | undefined; - - PUSHBULLET_ACCESS_TOKEN: string | undefined; - - PUSHOVER_USER_KEY: string | undefined; - PUSHOVER_API_TOKEN: string | undefined; - - SLACK_WEBHOOK_URL: string | undefined; - - TELEGRAM_BOT_TOKEN: string | undefined; - TELEGRAM_CHAT_ID: string | undefined; - - WHATSAPP_API_URL: string | undefined; - WHATSAPP_RECIPIENT: string | undefined; - - CUSTOM_NOTIFICATION: string | undefined; // enter the script name without .js here and without custom/... + NODE_ENV: "development" | "production" | "testing"; + PORT: string | undefined; + CI: "true" | null; } } } diff --git a/eslint.config.mjs b/eslint.config.mjs index 5b7b70a1..56994a62 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -5,7 +5,7 @@ import tseslint from "typescript-eslint"; /** @type {import('eslint').Linter.Config[]} */ export default [ { ignores: ["node_modules/*", "dist/*"] }, - { files: ["src/*.{ts}"] }, + { files: ["src/**/*.ts"] }, { languageOptions: { globals: globals.node } }, pluginJs.configs.recommended, ...tseslint.configs.recommended, diff --git a/nodemon.json b/nodemon.json index 9d946e97..be32c75d 100644 --- a/nodemon.json +++ b/nodemon.json @@ -4,7 +4,8 @@ "src/logs", "**/fixtures/**", ".gitignore", - "**/*.json" + "**/*.json", + "**/__tests__/**" ], "execMap": { "ts": "tsx" diff --git a/package-lock.json b/package-lock.json index 8c1ea14f..6efc7ed3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,40 +12,52 @@ "bcrypt": "^5.1.1", "chokidar": "^4.0.1", "cors": "^2.8.5", + "cytoscape": "^3.30.4", + "docker-compose": "^1.1.0", "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", "https": "^1.0.0", + "i": "^0.3.7", "ipaddr.js": "^2.2.0", "nodemailer": "^6.9.16", + "npm": "^11.0.0", + "puppeteer": "^24.0.0", "sqlite3": "^5.1.7", - "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "winston": "^3.15.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "yamljs": "^0.3.0" }, "devDependencies": { "@eslint/js": "^9.17.0", - "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", + "@types/cytoscape": "^3.21.8", "@types/dockerode": "^3.3.31", "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", + "@types/jest": "^29.5.14", "@types/node": "^22.9.0", + "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", + "@types/supertest": "^6.0.2", "@types/supports-color": "^8.1.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", + "@types/ws": "^8.5.14", + "@types/yamljs": "^0.2.34", "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", "dependency-cruiser": "^16.5.0", "eslint": "^9.17.0", "globals": "^15.14.0", + "jest": "^29.7.0", "license-checker": "^25.0.1", "nodemon": "^3.1.7", - "ora": "^8.1.1", "prettier": "^3.4.2", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript-eslint": "^8.18.2", @@ -55,314 +67,588 @@ "npm": ">=10.8.2" } }, - "node_modules/@apidevtools/json-schema-ref-parser": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", - "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", - "license": "MIT", + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "@jsdevtools/ono": "^7.1.3", - "@types/json-schema": "^7.0.6", - "call-me-maybe": "^1.0.1", - "js-yaml": "^4.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@apidevtools/openapi-schemas": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", - "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "node_modules/@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==", "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": ">=6.9.0" } }, - "node_modules/@apidevtools/swagger-methods": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", - "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", - "license": "MIT" + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } }, - "node_modules/@apidevtools/swagger-parser": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "node_modules/@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, "license": "MIT", "dependencies": { - "@apidevtools/json-schema-ref-parser": "^9.0.6", - "@apidevtools/openapi-schemas": "^2.0.4", - "@apidevtools/swagger-methods": "^3.0.2", - "@jsdevtools/ono": "^7.1.3", - "call-me-maybe": "^1.0.1", - "z-schema": "^5.0.1" + "@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" }, - "peerDependencies": { - "openapi-types": ">=7" + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@balena/dockerignore": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", - "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", - "license": "Apache-2.0" + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } }, - "node_modules/@colors/colors": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", - "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "node_modules/@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, "license": "MIT", + "dependencies": { + "@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" + }, "engines": { - "node": ">=0.1.90" + "node": ">=6.9.0" } }, - "node_modules/@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==", + "node_modules/@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, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "@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" }, "engines": { - "node": ">=12" + "node": ">=6.9.0" } }, - "node_modules/@dabh/diagnostics": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", - "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@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, "license": "MIT", "dependencies": { - "colorspace": "1.1.x", - "enabled": "2.0.x", - "kuler": "^2.0.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "dependencies": { + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@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==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], + "node_modules/@babel/helpers": { + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", + "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.7" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "dependencies": { + "@babel/types": "^7.26.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, "engines": { - "node": ">=18" + "node": ">=6.0.0" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], + "node_modules/@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, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, "engines": { - "node": ">=18" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "node_modules/@esbuild/linux-ppc64": { + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@balena/dockerignore": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", + "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", + "license": "Apache-2.0" + }, + "node_modules/@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, + "license": "MIT" + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", "cpu": [ "ppc64" ], @@ -370,50 +656,50 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "aix" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-riscv64": { + "node_modules/@esbuild/android-arm": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", "cpu": [ - "riscv64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-s390x": { + "node_modules/@esbuild/android-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", "cpu": [ - "s390x" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/linux-x64": { + "node_modules/@esbuild/android-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", "cpu": [ "x64" ], @@ -421,67 +707,67 @@ "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-x64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-arm64": { + "node_modules/@esbuild/darwin-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", "cpu": [ - "arm64" + "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "darwin" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/openbsd-x64": { + "node_modules/@esbuild/freebsd-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/sunos-x64": { + "node_modules/@esbuild/freebsd-x64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", "cpu": [ "x64" ], @@ -489,32 +775,253 @@ "license": "MIT", "optional": true, "os": [ - "sunos" + "freebsd" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-arm64": { + "node_modules/@esbuild/linux-arm": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", "cpu": [ - "arm64" + "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "linux" ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/win32-ia32": { + "node_modules/@esbuild/linux-arm64": { "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", "cpu": [ "ia32" @@ -576,13 +1083,13 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", - "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.5", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" }, @@ -615,9 +1122,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", - "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz", + "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -713,9 +1220,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", - "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz", + "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==", "dev": true, "license": "MIT", "engines": { @@ -723,9 +1230,9 @@ } }, "node_modules/@eslint/object-schema": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", - "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -733,18 +1240,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", - "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -752,6 +1273,37 @@ "license": "MIT", "optional": true }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.6", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.6.tgz", + "integrity": "sha512-JXUj6PI0oqqzTGvKtzOkxtpsyPRNsrmhh41TtIz/zEB6J+AUiZZ0dxWzcMwO9Ns5rmSPuMdghlTbUuqIM48d3Q==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -818,14 +1370,455 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@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, - "license": "MIT", - "engines": { - "node": ">=6.0.0" + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "engines": { + "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -836,21 +1829,25 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "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, "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@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==", - "license": "MIT" + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } }, "node_modules/@mapbox/node-pre-gyp": { "version": "1.0.11", @@ -949,40 +1946,167 @@ "node": ">=10" } }, - "node_modules/@playwright/test": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", - "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.49.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" }, - "node_modules/@tootallnate/once": { + "node_modules/@protobufjs/base64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 6" - } + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" }, - "node_modules/@tsconfig/node10": { + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@puppeteer/browsers": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.7.1.tgz", + "integrity": "sha512-MK7rtm8JjaxPN7Mf1JdZIZKPD2Z+W7osvrC1vjpvfOX1K0awDIHYbNi89f7eotp7eMUn2shWnt03HwVbriXtKQ==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.0", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.0", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-fs": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz", + "integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/@puppeteer/browsers/node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@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, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@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, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", @@ -1010,6 +2134,51 @@ "dev": true, "license": "MIT" }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, "node_modules/@types/bcrypt": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", @@ -1041,6 +2210,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cors": { "version": "2.8.17", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", @@ -1051,6 +2227,13 @@ "@types/node": "*" } }, + "node_modules/@types/cytoscape": { + "version": "3.21.9", + "resolved": "https://registry.npmjs.org/@types/cytoscape/-/cytoscape-3.21.9.tgz", + "integrity": "sha512-JyrG4tllI6jvuISPjHK9j2Xv/LTbnLekLke5otGStjFluIyA9JjgnvgZrSBsp8cEDpiTjwgZUZwpPv8TSBcoLw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/docker-modem": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", @@ -1063,9 +2246,9 @@ } }, "node_modules/@types/dockerode": { - "version": "3.3.32", - "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.32.tgz", - "integrity": "sha512-xxcG0g5AWKtNyh7I7wswLdFvym4Mlqks5ZlKzxEUrGHS0r0PUOfxm2T0mspwu10mHQqu3Ck3MI3V2HqvLWE1fg==", + "version": "3.3.34", + "resolved": "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.3.34.tgz", + "integrity": "sha512-mH9SuIb8NuTDsMus5epcbTzSbEo52fKLBMo0zapzYIAIyfDqoIFn7L3trekHLKC8qmxGV++pPUP4YqQ9n5v2Zg==", "dev": true, "license": "MIT", "dependencies": { @@ -1102,9 +2285,9 @@ "license": "MIT" }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.3.tgz", - "integrity": "sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", "dev": true, "license": "MIT", "dependencies": { @@ -1114,6 +2297,16 @@ "@types/send": "*" } }, + "node_modules/@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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -1121,10 +2314,56 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.14", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", + "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/mime": { @@ -1135,15 +2374,25 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz", - "integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==", - "dev": true, + "version": "22.13.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.1.tgz", + "integrity": "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==", "license": "MIT", "dependencies": { "undici-types": "~6.20.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/nodemailer": { "version": "6.4.17", "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", @@ -1155,9 +2404,9 @@ } }, "node_modules/@types/qs": { - "version": "6.9.17", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz", - "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==", + "version": "6.9.18", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", + "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", "dev": true, "license": "MIT" }, @@ -1192,9 +2441,9 @@ } }, "node_modules/@types/ssh2": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.1.tgz", - "integrity": "sha512-ZIbEqKAsi5gj35y4P4vkJYly642wIbY6PqoN0xiyQGshKUGXR9WQjF/iF9mXBQ8uBKy3ezfsCkcoHKhd0BzuDA==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.4.tgz", + "integrity": "sha512-9JTQgVBWSgq6mAen6PVnrAmty1lqgCMvpfN+1Ck5WRUsyMYPa6qd50/vMJ0y1zkGpOEgLzm8m8Dx/Y5vRouLaA==", "dev": true, "license": "MIT", "dependencies": { @@ -1202,9 +2451,9 @@ } }, "node_modules/@types/ssh2/node_modules/@types/node": { - "version": "18.19.69", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.69.tgz", - "integrity": "sha512-ECPdY1nlaiO/Y6GUnwgtAAhLNaQ53AyIVz+eILxpEo5OvuqE6yWkqWBIb5dU0DqhKQtMeny+FBD3PK6lm7L5xQ==", + "version": "18.19.75", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.75.tgz", + "integrity": "sha512-UIksWtThob6ZVSyxcOqCLOUNg/dyO1Qvx4McgeuhrEtHTLFTf7BBhEazaE4K806FGTPtzd/2sE90qn4fVr7cyw==", "dev": true, "license": "MIT", "dependencies": { @@ -1218,6 +2467,37 @@ "dev": true, "license": "MIT" }, + "node_modules/@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, + "license": "MIT" + }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.2.tgz", + "integrity": "sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/supports-color": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/@types/supports-color/-/supports-color-8.1.3.tgz", @@ -1249,22 +2529,66 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.14.tgz", + "integrity": "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yamljs": { + "version": "0.2.34", + "resolved": "https://registry.npmjs.org/@types/yamljs/-/yamljs-0.2.34.tgz", + "integrity": "sha512-gJvfRlv9ErxdOv7ux7UsJVePtX54NAvQyd8ncoiFqK8G5aeHIfQfGH2fbruvjAQ9657HwAaO54waS+Dsk2QTUQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz", - "integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz", + "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/type-utils": "8.19.0", - "@typescript-eslint/utils": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/type-utils": "8.23.0", + "@typescript-eslint/utils": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1280,16 +2604,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz", - "integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz", + "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/typescript-estree": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4" }, "engines": { @@ -1305,14 +2629,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz", - "integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz", + "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0" + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1323,16 +2647,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz", - "integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz", + "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.19.0", - "@typescript-eslint/utils": "8.19.0", + "@typescript-eslint/typescript-estree": "8.23.0", + "@typescript-eslint/utils": "8.23.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1347,9 +2671,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz", - "integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz", + "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==", "dev": true, "license": "MIT", "engines": { @@ -1361,20 +2685,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz", - "integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz", + "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/visitor-keys": "8.19.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/visitor-keys": "8.23.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1388,16 +2712,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz", - "integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz", + "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.19.0", - "@typescript-eslint/types": "8.19.0", - "@typescript-eslint/typescript-estree": "8.19.0" + "@typescript-eslint/scope-manager": "8.23.0", + "@typescript-eslint/types": "8.23.0", + "@typescript-eslint/typescript-estree": "8.23.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1412,13 +2736,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz", - "integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz", + "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.19.0", + "@typescript-eslint/types": "8.23.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1573,6 +2897,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1586,7 +2926,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1690,65 +3029,289 @@ "safer-buffer": "~2.1.0" } }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, + "node_modules/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, "license": "MIT", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" + "@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" }, "engines": { - "node": ">= 10.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" } }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "node_modules/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, "license": "BSD-3-Clause", "dependencies": { - "tweetnacl": "^0.14.3" + "@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" + }, + "engines": { + "node": ">=8" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "node_modules/babel-plugin-istanbul/node_modules/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, + "license": "BSD-3-Clause", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@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" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.0.1.tgz", + "integrity": "sha512-ilQs4fm/l9eMfWY2dY0WCIUplSUp7U0CT1vrqMg1MUdeZl4fypu5UP0XcDBK5WBQPJAKP1b7XEodISmekH/CEg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^3.0.0", + "bare-stream": "^2.0.0" + }, + "engines": { + "bare": ">=1.7.0" + } + }, + "node_modules/bare-os": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.4.0.tgz", + "integrity": "sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.6.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/bcrypt": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", + "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.11", + "node-addon-api": "^5.0.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "engines": { @@ -1840,6 +3403,62 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1864,6 +3483,22 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/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, + "license": "MIT" + }, "node_modules/buildcheck": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", @@ -1912,6 +3547,19 @@ "node": ">= 10" } }, + "node_modules/cacache/node_modules/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==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/cacache/node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1925,6 +3573,13 @@ "node": ">=10" } }, + "node_modules/cacache/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", @@ -1954,22 +3609,46 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-me-maybe": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", - "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "license": "MIT" - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001698", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001698.tgz", + "integrity": "sha512-xJ3km2oiG/MbNU8G6zIq6XRZ6HtAOVXsbOrP/blGazi52kc5Yy7b6sDA5O+FbROzRrV7BSTllLHuNvmawYUJjw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1987,6 +3666,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2011,6 +3700,42 @@ "node": ">=10" } }, + "node_modules/chromium-bidi": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-1.2.0.tgz", + "integrity": "sha512-XtdJ1GSN6S3l7tO7F77GhNsw0K367p0IsLYf2yZawCVAKKC3lUvDhPdMVrB2FNhmhfW43QGYbEX3Wg6q0maGwQ==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -2021,35 +3746,38 @@ "node": ">=6" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", + "node_modules/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==", + "license": "ISC", "dependencies": { - "restore-cursor": "^5.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -2064,7 +3792,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2123,16 +3850,39 @@ "text-hex": "1.0.x" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2166,6 +3916,13 @@ "node": ">= 0.6" } }, + "node_modules/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, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", @@ -2181,6 +3938,13 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/cors": { "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", @@ -2194,6 +3958,32 @@ "node": ">= 0.10" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/cpu-features": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", @@ -2208,6 +3998,28 @@ "node": ">=10.0.0" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@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" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2230,6 +4042,24 @@ "node": ">= 8" } }, + "node_modules/cytoscape": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.31.0.tgz", + "integrity": "sha512-zDGn1K/tfZwEnoGOcHc0H4XazqAAXAuDpcYw9mUnUjATjqljyCNGJv8uEvbvxGaGHaVshxMecyl6oc6uKzRfbw==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2273,6 +4103,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -2289,6 +4134,40 @@ "dev": true, "license": "MIT" }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -2305,9 +4184,9 @@ } }, "node_modules/dependency-cruiser": { - "version": "16.8.0", - "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.8.0.tgz", - "integrity": "sha512-VyBzIrLHfG7rT36URln+CTy8VSjrLB7YDlMx5vtBSHRHCOXgLUCcP4n5ZoD+s166T0i5LN33q1CvBkEOGsDTSg==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-16.9.0.tgz", + "integrity": "sha512-Gc/xHNOBq1nk5i7FPCuexCD0m2OXB/WEfiSHfNYQaQaHZiZltnl5Ixp/ZG38Jvi8aEhKBQTHV4Aw6gmR7rWlOw==", "dev": true, "license": "MIT", "dependencies": { @@ -2317,9 +4196,9 @@ "acorn-loose": "^8.4.0", "acorn-walk": "^8.3.4", "ajv": "^8.17.1", - "commander": "^12.1.0", - "enhanced-resolve": "^5.17.1", - "ignore": "^6.0.2", + "commander": "^13.0.0", + "enhanced-resolve": "^5.18.0", + "ignore": "^7.0.0", "interpret": "^3.1.1", "is-installed-globally": "^1.0.0", "json5": "^2.2.3", @@ -2347,9 +4226,9 @@ } }, "node_modules/dependency-cruiser/node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", "dev": true, "license": "MIT", "engines": { @@ -2375,6 +4254,22 @@ "node": ">=8" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1402036", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1402036.tgz", + "integrity": "sha512-JwAYQgEvm3yD45CHB+RmF5kMbWtXBaOGwuxa87sZogHcLCv8c/IqnThaoQ1y60d7pXWjSKWQphPEc+1rAScVdg==", + "license": "BSD-3-Clause" + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -2396,10 +4291,32 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/docker-compose": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-1.1.0.tgz", + "integrity": "sha512-VrkQJNafPQ5d6bGULW0P6KqcxSkv3ZU5Wn2wQA19oB71o7+55vQ9ogFe2MMeNbK+jc9rrKVy280DnHO5JLMWOQ==", + "license": "MIT", + "dependencies": { + "yaml": "^2.2.2" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/docker-modem": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.3.tgz", - "integrity": "sha512-89zhop5YVhcPEt5FpUFGr3cDyceGhq/F9J+ZndQ4KfqNvfbJpPMfgeixFgUj5OjCYAboElqODxY5Z1EBsSa6sg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", + "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", "license": "Apache-2.0", "dependencies": { "debug": "^4.1.1", @@ -2412,31 +4329,23 @@ } }, "node_modules/dockerode": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.2.tgz", - "integrity": "sha512-9wM1BVpVMFr2Pw3eJNXrYYt6DT9k0xMcsSCjtPvyQ+xa1iPg/Mo3T/gUcwI0B2cczqCeCYRPF8yFYDwtFXT0+w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.4.tgz", + "integrity": "sha512-6GYP/EdzEY50HaOxTVTJ2p+mB5xDHTMJhS+UoGrVyS6VC+iQRh7kZ4FRpUYq6nziby7hPqWhOrFFUFTMUZJJ5w==", "license": "Apache-2.0", "dependencies": { "@balena/dockerignore": "^1.0.2", - "docker-modem": "^5.0.3", - "tar-fs": "~2.0.1" + "@grpc/grpc-js": "^1.11.1", + "@grpc/proto-loader": "^0.7.13", + "docker-modem": "^5.0.6", + "protobufjs": "^7.3.2", + "tar-fs": "~2.0.1", + "uuid": "^10.0.0" }, "engines": { "node": ">= 8.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2457,6 +4366,42 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.96", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.96.tgz", + "integrity": "sha512-8AJUW6dh75Fm/ny8+kZKJzI1pgoE8bKLZlzDU2W1ENd+DXKJrx7I7l9hb8UWR4ojlnb5OlixMt00QWiYJoVw1w==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2511,9 +4456,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", "dev": true, "license": "MIT", "dependencies": { @@ -2529,7 +4474,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "license": "MIT", - "optional": true, "engines": { "node": ">=6" } @@ -2541,6 +4485,15 @@ "license": "MIT", "optional": true }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2560,9 +4513,9 @@ } }, "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2611,6 +4564,15 @@ "@esbuild/win32-x64": "0.23.1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -2630,20 +4592,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, "node_modules/eslint": { - "version": "9.17.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", - "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", + "version": "9.20.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.0.tgz", + "integrity": "sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.11.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.17.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.20.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -2812,6 +4795,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -2842,7 +4838,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -2866,6 +4861,39 @@ "node": ">= 0.6" } }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2875,6 +4903,23 @@ "node": ">=6" } }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/express": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", @@ -2951,6 +4996,41 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extract-zip/node_modules/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==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2958,10 +5038,16 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, "license": "MIT", "dependencies": { @@ -2969,7 +5055,7 @@ "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -3002,28 +5088,64 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", - "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", - "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.0.tgz", + "integrity": "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==", "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, - "node_modules/fecha": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", - "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", - "license": "MIT" + "node_modules/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, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", @@ -3053,6 +5175,29 @@ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/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, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -3143,6 +5288,36 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", "license": "MIT" }, + "node_modules/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", + "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dezalgo": "^1.0.4", + "hexoid": "^2.0.0", + "once": "^1.4.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3186,9 +5361,9 @@ "license": "ISC" }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3230,17 +5405,23 @@ "node": ">=10" } }, - "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "node_modules/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, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6.9.0" + } + }, + "node_modules/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==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { @@ -3267,10 +5448,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/get-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.0.tgz", - "integrity": "sha512-TtLgOcKaF1nMP2ijJnITkE4nRhbpshHhmzKiuhmSniiwWzovoqwqQ8rNuhf0mXJOqIY5iU+QkUe0CkJYrLsG9w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3280,10 +5471,23 @@ "node": ">= 0.4" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-tsconfig": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz", - "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.0.tgz", + "integrity": "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A==", "dev": true, "license": "MIT", "dependencies": { @@ -3293,6 +5497,20 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -3450,6 +5668,16 @@ "node": ">= 0.4" } }, + "node_modules/hexoid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", + "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/hosted-git-info": { "version": "2.8.9", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", @@ -3457,6 +5685,13 @@ "dev": true, "license": "ISC" }, + "node_modules/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, + "license": "MIT" + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -3481,18 +5716,25 @@ } }, "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "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==", "license": "MIT", - "optional": true, "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" + } + }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" } }, "node_modules/https": { @@ -3514,6 +5756,16 @@ "node": ">= 6" } }, + "node_modules/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, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -3524,6 +5776,14 @@ "ms": "^2.0.0" } }, + "node_modules/i": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.7.tgz", + "integrity": "sha512-FYz4wlXgkQwIPqhzC5TdNMLSE5+GS1IIDJZY/1ZiEPCT2S3COUVZeT5OW4BmW4r5LHLQuOosSwsvnroG9GR59Q==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3574,10 +5834,9 @@ "license": "ISC" }, "node_modules/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, + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -3590,6 +5849,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -3659,7 +5938,6 @@ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", "license": "MIT", - "optional": true, "dependencies": { "jsbn": "1.1.0", "sprintf-js": "^1.1.3" @@ -3678,9 +5956,9 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "license": "MIT" }, "node_modules/is-binary-path": { @@ -3731,6 +6009,16 @@ "node": ">=8" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3761,19 +6049,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", @@ -3816,19 +6091,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3836,490 +6098,637 @@ "devOptional": true, "license": "ISC" }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "license": "MIT", + "node_modules/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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "license": "BSD-3-Clause", "dependencies": { - "argparse": "^2.0.1" + "@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" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=10" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT", - "optional": true - }, - "node_modules/json-buffer": { + "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "MIT" - }, - "node_modules/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, - "license": "MIT" - }, - "node_modules/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "node_modules/istanbul-lib-report/node_modules/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, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "node_modules/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, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, "engines": { - "node": ">=6" + "node": ">=10" } }, - "node_modules/kuler": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", - "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", - "license": "MIT" - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "node_modules/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, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">=8" } }, - "node_modules/license-checker": { - "version": "25.0.1", - "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", - "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "read-installed": "~4.0.3", - "semver": "^5.5.0", - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0", - "spdx-satisfies": "^4.0.0", - "treeify": "^1.1.0" + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" }, "bin": { - "license-checker": "bin/license-checker" + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" } }, - "node_modules/license-checker/node_modules/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==", + "node_modules/jake/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^1.9.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/jake/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/license-checker/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/license-checker/node_modules/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==", + "node_modules/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, "license": "MIT", "dependencies": { - "color-name": "1.1.3" + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/license-checker/node_modules/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, - "license": "MIT" - }, - "node_modules/license-checker/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/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, "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/license-checker/node_modules/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==", + "node_modules/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, "license": "MIT", + "dependencies": { + "@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" + }, + "bin": { + "jest": "bin/jest.js" + }, "engines": { - "node": ">=0.8.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/license-checker/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "node_modules/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, "license": "MIT", + "dependencies": { + "@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" + }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } } }, - "node_modules/license-checker/node_modules/nopt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", - "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "node_modules/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, - "license": "ISC", + "license": "MIT", "dependencies": { - "abbrev": "1", - "osenv": "^0.1.4" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "bin": { - "nopt": "bin/nopt.js" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/license-checker/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/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, - "license": "ISC", - "bin": { - "semver": "bin/semver" + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/license-checker/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/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, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "@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" }, "engines": { - "node": ">=4" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/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==", + "node_modules/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, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "@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" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "license": "MIT" - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "license": "MIT" - }, - "node_modules/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==", + "node_modules/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, - "license": "MIT" - }, - "node_modules/lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "license": "MIT" + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } }, - "node_modules/log-symbols": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", - "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "node_modules/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, "license": "MIT", "dependencies": { - "chalk": "^5.3.0", - "is-unicode-supported": "^1.3.0" + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "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" }, "engines": { - "node": ">=18" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "fsevents": "^2.3.2" } }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "node_modules/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, "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "node_modules/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, "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/logform": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", - "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "node_modules/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, "license": "MIT", "dependencies": { - "@colors/colors": "1.6.0", - "@types/triple-beam": "^1.3.2", - "fecha": "^4.2.0", - "ms": "^2.1.1", - "safe-stable-stringify": "^2.3.1", - "triple-beam": "^1.3.0" + "@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" }, "engines": { - "node": ">= 12.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/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==", - "license": "ISC", - "optional": true, + "node_modules/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, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" }, "engines": { - "node": ">=10" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/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==", + "node_modules/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, "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, "engines": { - "node": ">=8" + "node": ">=6" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } } }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "node_modules/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, - "license": "ISC" - }, - "node_modules/make-fetch-happen": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", - "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "license": "ISC", - "optional": true, - "dependencies": { - "agentkeepalive": "^4.1.3", - "cacache": "^15.2.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^6.0.0", - "minipass": "^3.1.3", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^1.3.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.2", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^6.0.0", - "ssri": "^8.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/memoize": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", - "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", + "node_modules/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, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "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" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sindresorhus/memoize?sponsor=1" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "node_modules/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, "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@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" + }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "node_modules/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, "license": "MIT", "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" + "@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" }, "engines": { - "node": ">=8.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/micromatch/node_modules/picomatch": { + "node_modules/jest-util/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", @@ -4332,57 +6741,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/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, "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "@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" }, "engines": { - "node": ">= 0.6" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", "engines": { "node": ">=10" }, @@ -4390,507 +6772,3688 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "node_modules/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, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", "dependencies": { - "yallist": "^4.0.0" + "@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" }, "engines": { - "node": ">=8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "license": "ISC", - "optional": true, + "node_modules/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, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" }, "engines": { - "node": ">= 8" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/minipass-fetch": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", - "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "node_modules/jest-worker/node_modules/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, "license": "MIT", - "optional": true, "dependencies": { - "minipass": "^3.1.0", - "minipass-sized": "^1.0.3", - "minizlib": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" }, - "optionalDependencies": { - "encoding": "^0.1.12" + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=8" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" }, "engines": { - "node": ">=8" + "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/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": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-checker": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", + "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker": "bin/license-checker" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT" + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/license-checker/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/license-checker/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/license-checker/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/long": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.4.tgz", + "integrity": "sha512-qtzLbJE8hq7VabR3mISmVGtoXP8KGc2Z/AT8OuqlYD7JTR3oqrgwdjnk07wpj1twXxYmgDXgoKVWUG/fReSzHg==", + "license": "Apache-2.0" + }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/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, + "license": "ISC" + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/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==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC", + "optional": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memoize": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz", + "integrity": "sha512-H6cBLgsi6vMWOcCpvVCdFFnl3kerEXbrYh9q+lY6VXvQSmM6CkmV08VOwT+WE2tzIEqRPFfAq3fm4v/UIW6mSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/memoize?sponsor=1" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "license": "MIT", + "optional": true + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-abi": { + "version": "3.74.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.74.0.tgz", + "integrity": "sha512-c5XK0MjkGBrQPGYG24GBADZud0NCbznxNx0ZkS+ebUTrmV1qTDxPxSL8zEAPURXSbLRWVexxmP4986BziahL5w==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT" + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/node-gyp/node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/node-gyp/node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/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, + "license": "MIT" + }, + "node_modules/nodemailer": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.0.tgz", + "integrity": "sha512-SQ3wZCExjeSatLE/HBaXS5vqUOQk6GtBdIIKxiFdmm01mOQZX/POJkO3SUX1wDiYcwUOJwT23scFSC9fY2H8IA==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "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" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/node_modules/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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/nodemon/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/nodemon/node_modules/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, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/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, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-11.1.0.tgz", + "integrity": "sha512-rPMBrZud26lI/LcjQeLw/K5Hf1apXMKgkpNNEzp0YQYmM877+T1ZNKPcB2hnTi7e6fBNz8xLtMMn/w46fVUqGw==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^9.0.0", + "@npmcli/config": "^10.0.1", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.1.1", + "@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.4.1", + "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": "^8.0.0", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^10.0.0", + "libnpmdiff": "^8.0.0", + "libnpmexec": "^10.0.0", + "libnpmfund": "^7.0.0", + "libnpmorg": "^8.0.0", + "libnpmpack": "^9.0.0", + "libnpmpublish": "^11.0.0", + "libnpmsearch": "^9.0.0", + "libnpmteam": "^8.0.0", + "libnpmversion": "^8.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.1", + "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": "^7.0.3", + "pacote": "^21.0.0", + "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" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true, + "license": "ISC" + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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": "^9.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-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": "^21.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": "^4.0.0" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "10.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^21.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.1.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^6.1.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.3.3", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.0.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/@tufjs/models": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/cacache/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.1.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "7.0.0", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.4.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "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" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.1.1", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.1.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" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^3.0.0", + "diff": "^7.0.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^21.0.0", + "tar": "^6.2.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.0", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^21.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": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^9.0.0", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^21.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "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" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minizlib": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/mkdirp": { + "version": "1.0.4", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "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" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/mkdirp": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/tar": { + "version": "7.4.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/node-gyp/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/nopt/node_modules/abbrev": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch/node_modules/minizlib": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.4", + "rimraf": "^5.0.5" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "21.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@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": "^10.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" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-inflight": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "err-code": "^2.0.2", + "retry": "^0.12.0" }, "engines": { - "node": ">= 8" + "node": ">=10" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "minimist": "^1.2.6" + "read": "^4.0.0" }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, "bin": { - "mkdirp": "bin/cmd.js" + "qrcode-terminal": "bin/qrcode-terminal.js" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" + "node_modules/npm/node_modules/read": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, "license": "MIT", "engines": { - "node": "*" + "node": ">= 4" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "node_modules/npm/node_modules/rimraf": { + "version": "5.0.10", + "inBundle": true, + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/nan": { - "version": "2.22.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", - "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, "license": "MIT", "optional": true }, - "node_modules/napi-build-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", - "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", - "license": "MIT" + "node_modules/npm/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/node-abi": { - "version": "3.71.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", - "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.0.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@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" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.3", + "inBundle": true, "license": "MIT", "dependencies": { - "semver": "^7.3.5" + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" }, "engines": { - "node": ">=10" + "node": ">= 10.0.0", + "npm": ">= 3.0.0" } }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", - "license": "MIT" + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.21", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" }, "engines": { - "node": "4.x || >=6.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependencies": { - "encoding": "^0.1.0" + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "engines": { + "node": ">=8" } }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, "license": "MIT", - "optional": true, "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" + "ansi-regex": "^5.0.1" }, - "bin": { - "node-gyp": "bin/node-gyp.js" + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 10.12.0" + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/node-gyp/node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", + "node_modules/npm/node_modules/tar": { + "version": "6.2.1", + "inBundle": true, "license": "ISC", - "optional": true, "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "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" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=10" } }, - "node_modules/node-gyp/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "inBundle": true, "license": "ISC", - "optional": true, "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "minipass": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">= 8" } }, - "node_modules/node-gyp/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", + "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, "license": "ISC", - "optional": true, "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "yallist": "^4.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/nodemailer": { - "version": "6.9.16", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", - "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", - "license": "MIT-0", + "node_modules/npm/node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=6.0.0" + "node": ">=8" } }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "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" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.0.1", + "inBundle": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@tufjs/models": "3.0.1", + "debug": "^4.3.6", + "make-fetch-happen": "^14.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.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" + "unique-slug": "^5.0.0" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/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, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "inBundle": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "imurmurhash": "^0.1.4" }, "engines": { - "node": ">= 6" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", "engines": { - "node": ">=4" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "node_modules/npm/node_modules/walk-up-path": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "inBundle": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" }, "engines": { - "node": "*" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/nodemon/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/nodemon/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/nodemon/node_modules/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, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "dependencies": { - "abbrev": "1" + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" }, - "bin": { - "nopt": "bin/nopt.js" + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" }, "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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, - "license": "BSD-2-Clause", + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "inBundle": true, + "license": "MIT", "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "inBundle": true, "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/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, - "license": "MIT", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, "engines": { - "node": ">=0.10.0" + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/npm-normalize-package-bin": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", - "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true, + "node_modules/npm/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, "license": "ISC" }, "node_modules/npmlog": { @@ -4925,9 +10488,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -4967,28 +10530,21 @@ } }, "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, "license": "MIT", "dependencies": { - "mimic-function": "^5.0.0" + "mimic-fn": "^2.1.0" }, "engines": { - "node": ">=18" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/openapi-types": { - "version": "12.1.3", - "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", - "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "license": "MIT", - "peer": true - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5007,97 +10563,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-8.1.1.tgz", - "integrity": "sha512-YWielGi1XzG1UTvOaCFaNgEnuhZVMSHYkW/FQ7UX8O26PtlpdM84c0f7wLPlkvx2RfiQmnzd61d/MGxmpQeJPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.3.0", - "cli-cursor": "^5.0.0", - "cli-spinners": "^2.9.2", - "is-interactive": "^2.0.0", - "is-unicode-supported": "^2.0.0", - "log-symbols": "^6.0.0", - "stdin-discarder": "^0.2.2", - "string-width": "^7.2.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, - "node_modules/ora/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", @@ -5178,11 +10643,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.1.0.tgz", + "integrity": "sha512-Z5FnLVVZSnX7WjBg0mhDtydeRZ1xMcATZThjySQUHqr+0ksP8kqaw23fNKkaaN/Z8gwLUs/W7xdl0I75eP2Xyw==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/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, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -5191,6 +10719,24 @@ "node": ">=6" } }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "@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" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5242,11 +10788,16 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -5262,42 +10813,89 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/playwright": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", - "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "node_modules/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, - "license": "Apache-2.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/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, + "license": "MIT", "dependencies": { - "playwright-core": "1.49.1" + "find-up": "^4.0.0" }, - "bin": { - "playwright": "cli.js" + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/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, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/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, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" }, - "optionalDependencies": { - "fsevents": "2.3.2" + "engines": { + "node": ">=8" } }, - "node_modules/playwright-core": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", - "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "node_modules/pkg-dir/node_modules/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, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/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, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/prebuild-install": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", - "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -5305,7 +10903,7 @@ "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", - "napi-build-utils": "^1.0.1", + "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", @@ -5346,6 +10944,43 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -5381,28 +11016,108 @@ "node": ">= 6" } }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/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==", "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/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==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { - "node": ">= 0.10" + "node": ">= 14" } }, - "node_modules/proxy-addr/node_modules/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==", - "license": "MIT", + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", "engines": { - "node": ">= 0.10" + "node": ">=12" } }, + "node_modules/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==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -5430,6 +11145,61 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.2.0.tgz", + "integrity": "sha512-z8vv7zPEgrilIbOo3WNvM+2mXMnyM9f4z6zdrB88Fzeuo43Oupmjrzk3EpuvuCtyK0A7Lsllfx7Z+4BvEEGJcQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.7.1", + "chromium-bidi": "1.2.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1402036", + "puppeteer-core": "24.2.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.2.0.tgz", + "integrity": "sha512-e4A4/xqWdd4kcE6QVHYhJ+Qlx/+XpgjP4d8OwBx0DJoY/nkIRhSgYmKQnv7+XSs1ofBstalt+XPGrkaz4FoXOQ==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.7.1", + "chromium-bidi": "1.2.0", + "debug": "^4.4.0", + "devtools-protocol": "0.0.1402036", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -5520,6 +11290,13 @@ "node": ">=0.10.0" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, "node_modules/read-installed": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", @@ -5592,12 +11369,12 @@ } }, "node_modules/readdirp": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", - "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.1.tgz", + "integrity": "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw==", "license": "MIT", "engines": { - "node": ">= 14.16.0" + "node": ">= 14.18.0" }, "funding": { "type": "individual", @@ -5627,6 +11404,15 @@ "regexp-tree": "bin/regexp-tree" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5658,11 +11444,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/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, "license": "MIT", "engines": { "node": ">=4" @@ -5678,34 +11486,14 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "node_modules/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, "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/restore-cursor/node_modules/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, - "license": "ISC", "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/retry": { @@ -5815,9 +11603,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6056,6 +11844,12 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -6076,6 +11870,16 @@ "dev": true, "license": "MIT" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -6091,7 +11895,6 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", - "optional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -6102,7 +11905,6 @@ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", "license": "MIT", - "optional": true, "dependencies": { "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" @@ -6113,18 +11915,47 @@ } }, "node_modules/socks-proxy-agent": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", - "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "license": "MIT", - "optional": true, "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" }, "engines": { - "node": ">= 10" + "node": ">= 14" + } + }, + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/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, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "node_modules/spdx-compare": { @@ -6169,9 +12000,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.20", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "version": "3.0.21", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", + "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", "dev": true, "license": "CC0-1.0" }, @@ -6204,8 +12035,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause", - "optional": true + "license": "BSD-3-Clause" }, "node_modules/sqlite3": { "version": "5.1.7", @@ -6276,6 +12106,29 @@ "node": "*" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6285,17 +12138,17 @@ "node": ">= 0.8" } }, - "node_modules/stdin-discarder": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", - "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", - "dev": true, + "node_modules/streamx": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.0.tgz", + "integrity": "sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw==", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { @@ -6307,6 +12160,20 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -6334,13 +12201,23 @@ } }, "node_modules/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==", + "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, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=8" + } + }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/strip-json-comments": { @@ -6356,120 +12233,84 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/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==", + "node_modules/superagent": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", + "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.4", + "debug": "^4.3.4", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.0", + "formidable": "^3.5.1", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.11.0" }, "engines": { - "node": ">=8" + "node": ">=14.18.0" } }, - "node_modules/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==", + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/swagger-jsdoc": { - "version": "6.2.8", - "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", - "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", - "license": "MIT", - "dependencies": { - "commander": "6.2.0", - "doctrine": "3.0.0", - "glob": "7.1.6", - "lodash.mergewith": "^4.6.2", - "swagger-parser": "^10.0.3", - "yaml": "2.0.0-1" - }, "bin": { - "swagger-jsdoc": "bin/swagger-jsdoc.js" + "mime": "cli.js" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/swagger-jsdoc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": ">=4.0.0" } }, - "node_modules/swagger-jsdoc/node_modules/commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "node_modules/supertest": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", + "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/swagger-jsdoc/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "methods": "^1.1.2", + "superagent": "^9.0.1" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=14.18.0" } }, - "node_modules/swagger-jsdoc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "license": "ISC", + "node_modules/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, + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "has-flag": "^4.0.0" }, "engines": { - "node": "*" + "node": ">=8" } }, - "node_modules/swagger-parser": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", - "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "node_modules/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==", + "dev": true, "license": "MIT", - "dependencies": { - "@apidevtools/swagger-parser": "10.0.3" - }, "engines": { - "node": ">=10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/swagger-ui-dist": { - "version": "5.18.2", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz", - "integrity": "sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw==", + "version": "5.18.3", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.18.3.tgz", + "integrity": "sha512-G33HFW0iFNStfY2x6QXO2JYVMrFruc8AZRX0U/L71aA7WeWfX2E5Nm8E/tsipSZJeIZZbSjUDeynLK/wcuNWIw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -6572,6 +12413,12 @@ "node": ">=10" } }, + "node_modules/tar/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/teamcity-service-messages": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/teamcity-service-messages/-/teamcity-service-messages-0.1.14.tgz", @@ -6579,12 +12426,67 @@ "dev": true, "license": "MIT" }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", "license": "MIT" }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6643,16 +12545,65 @@ } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/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, + "license": "MIT", + "dependencies": { + "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" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { - "typescript": ">=4.2.0" + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } } }, "node_modules/ts-node": { @@ -6730,6 +12681,22 @@ "node": ">=10.13.0" } }, + "node_modules/tsconfig-paths/node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/tsx": { "version": "4.19.2", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz", @@ -6750,21 +12717,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6796,6 +12748,29 @@ "node": ">= 0.8.0" } }, + "node_modules/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, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/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, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6809,11 +12784,17 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "devOptional": true, "license": "Apache-2.0", "peer": true, "bin": { @@ -6825,15 +12806,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.19.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.0.tgz", - "integrity": "sha512-Ni8sUkVWYK4KAcTtPjQ/UTiRk6jcsuDhPpxULapUDi8A/l8TSBk+t1GtJA1RsCzIJg0q6+J7bf35AwQigENWRQ==", + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.23.0.tgz", + "integrity": "sha512-/LBRo3HrXr5LxmrdYSOCvoAMm7p2jNizNfbIpCgvG4HMsnoprRUOce/+8VJ9BDYWW68rqIENE/haVLWPeFZBVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.19.0", - "@typescript-eslint/parser": "8.19.0", - "@typescript-eslint/utils": "8.19.0" + "@typescript-eslint/eslint-plugin": "8.23.0", + "@typescript-eslint/parser": "8.23.0", + "@typescript-eslint/utils": "8.23.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6871,7 +12852,6 @@ "version": "6.20.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", - "dev": true, "license": "MIT" }, "node_modules/unique-filename": { @@ -6903,6 +12883,37 @@ "node": ">= 0.8" } }, + "node_modules/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, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6935,6 +12946,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/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", @@ -6942,6 +12966,21 @@ "dev": true, "license": "MIT" }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, "node_modules/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", @@ -6953,15 +12992,6 @@ "spdx-expression-parse": "^3.0.0" } }, - "node_modules/validator": { - "version": "13.12.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", - "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -6971,6 +13001,16 @@ "node": ">= 0.8" } }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, "node_modules/watskeburt": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-4.2.2.tgz", @@ -7089,25 +13129,156 @@ "node": ">=0.10.0" } }, + "node_modules/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==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/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, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" }, "node_modules/yaml": { - "version": "2.0.0-1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", - "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", + "integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==", "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" + } + }, + "node_modules/yamljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yamljs/-/yamljs-0.3.0.tgz", + "integrity": "sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yamljs/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/yamljs/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "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.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/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==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, "node_modules/yn": { @@ -7133,34 +13304,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/z-schema": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", - "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", - "license": "MIT", - "dependencies": { - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "validator": "^13.7.0" - }, - "bin": { - "z-schema": "bin/z-schema" - }, - "engines": { - "node": ">=8.0.0" - }, - "optionalDependencies": { - "commander": "^9.4.1" - } - }, - "node_modules/z-schema/node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "node_modules/zod": { + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "license": "MIT", - "optional": true, - "engines": { - "node": "^12.20.0 || >=14" + "funding": { + "url": "https://github.com/sponsors/colinhacks" } } } diff --git a/package.json b/package.json index 6b38fd68..c48ee738 100644 --- a/package.json +++ b/package.json @@ -1,24 +1,31 @@ { "name": "dockstatapi", + "repository": "git@github.com:Its4Nik/dockstatapi.git", "version": "2.0.1", "description": "API for docker hosts using dockerode", "main": "src/server.ts", "scripts": { + "test": "NODE_ENV=testing jest -w 1 --forceExit", + "test:silent": "NODE_ENV=testing jest -w 1 --forceExit --silent", "local-env-file": "bash ./src/misc/createEnvDev.sh", - "start": "npm run local-env-file && tsx src/server.ts", - "dev": "npm run local-env-file && nodemon", - "dev:trace": "npm run local-env-file && nodemon --trace-uncaught --trace-warnings", + "start": "npm run local-env-file && NODE_ENV=production tsx src/server.ts", + "start:build": "npm run local-env-file -d && npm run build && NODE_ENV=production node dist/src/src/server.js", + "dev": "npm run local-env-file && NODE_ENV=development nodemon", + "dev:socket": "docker compose -f docker/docker-compose.dev.yaml up -d && npm run local-env-file && NODE_ENV=development nodemon ; docker compose -f docker/docker-compose.dev.yaml down", + "dev:trace": "npm run local-env-file && NODE_ENV=development nodemon --trace-uncaught --trace-warnings", "dep": "bash ./src/misc/dependencyGraphs/createDependencyGraph.sh", "dep:remove": "bash ./src/misc/removeUnusedDeps.sh && npm run dep", - "build": "npx tsc", - "build:mini": "npx tsc && bash ./src/misc/minifyDist.sh --build-only", + "build": "tsc", + "build:mini": "tsc && bash ./src/misc/minifyDist.sh --build-only", "build:docker": "docker build . -t \"dockstatapi:local\" -f ./docker/Dockerfile-dev", + "build:docker:prod": "docker build . -t \"dockstatapi:local\" -f ./docker/Dockerfile-base", "mini": "bash ./src/misc/minifyDist.sh", "docker": "docker compose -f docker/docker-compose.yaml up -d && bash ./src/misc/.tmux.sh; docker compose -f docker/docker-compose.yaml down", "docker:build": "npm run build:docker && npm run docker", - "prettier": "npx prettier -c ./src/**/*.ts --parser typescript --write && npx prettier -c ./.github/workflows/*.yaml --parser yaml --write && npx prettier -c ./**/*.md --parser markdown --write && npx prettier -c ./**/*.json --parser json --write", - "lint": "npx eslint", - "lint:fix": "npx eslint --fix", + "docker:build:prod": "npm run build:docker:prod && npm run docker", + "prettier": "prettier -c ./__tests__/*.spec.ts --parser typescript --write && prettier -c ./src/**/*.ts --parser typescript --write && prettier -c ./.github/workflows/*.yaml --parser yaml --write && prettier -c ./**/*.md --parser markdown --write && prettier -c ./**/*.json --parser json --write", + "lint": "eslint", + "lint:fix": "eslint --fix", "license": "bash ./src/misc/credits.sh", "finish": "npm run local-env-file && npm run license && npm run prettier && npm run lint" }, @@ -29,40 +36,52 @@ "bcrypt": "^5.1.1", "chokidar": "^4.0.1", "cors": "^2.8.5", + "cytoscape": "^3.30.4", + "docker-compose": "^1.1.0", "dockerode": "^4.0.2", "express": "^4.21.1", "express-rate-limit": "^7.4.1", "https": "^1.0.0", + "i": "^0.3.7", "ipaddr.js": "^2.2.0", "nodemailer": "^6.9.16", + "npm": "^11.0.0", + "puppeteer": "^24.0.0", "sqlite3": "^5.1.7", - "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "winston": "^3.15.0", - "winston-daily-rotate-file": "^5.0.0" + "winston-daily-rotate-file": "^5.0.0", + "yamljs": "^0.3.0" }, "devDependencies": { "@eslint/js": "^9.17.0", - "@playwright/test": "^1.49.0", "@types/bcrypt": "^5.0.2", "@types/cors": "^2.8.17", + "@types/cytoscape": "^3.21.8", "@types/dockerode": "^3.3.31", "@types/express": "^5.0.0", "@types/express-handlebars": "^5.3.1", + "@types/jest": "^29.5.14", "@types/node": "^22.9.0", + "@types/node-fetch": "^2.6.12", "@types/nodemailer": "^6.4.17", + "@types/supertest": "^6.0.2", "@types/supports-color": "^8.1.3", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", + "@types/ws": "^8.5.14", + "@types/yamljs": "^0.2.34", "@typescript-eslint/eslint-plugin": "^8.18.2", "@typescript-eslint/parser": "^8.18.2", "dependency-cruiser": "^16.5.0", "eslint": "^9.17.0", "globals": "^15.14.0", + "jest": "^29.7.0", "license-checker": "^25.0.1", "nodemon": "^3.1.7", - "ora": "^8.1.1", "prettier": "^3.4.2", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "tsx": "^4.19.2", "typescript-eslint": "^8.18.2", @@ -71,5 +90,34 @@ "engines": { "npm": ">=10.8.2" }, - "repository": "git@github.com:Its4Nik/dockstatapi.git" + "jest": { + "preset": "ts-jest", + "testMatch": [ + "**/__tests__/**/*.(test|spec).ts" + ], + "testEnvironment": "node", + "transform": { + "^.+\\.(ts|tsx)$": "ts-jest" + }, + "moduleFileExtensions": [ + "ts", + "tsx", + "js", + "jsx", + "json", + "node" + ], + "coveragePathIgnorePatterns": [ + "/node_modules/" + ], + "moduleNameMapper": { + "^@/(.*)$": "src/$1" + }, + "transformIgnorePatterns": [ + "/node_modules/" + ], + "testPathIgnorePatterns": [ + "util" + ] + } } diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index 2c33a93e..00000000 --- a/playwright.config.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { defineConfig, devices } from '@playwright/test'; - -export default defineConfig({ - timeout: 300000, - testDir: './tests', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: 'html', - use: { - trace: 'on-first-retry', - }, - - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - ], - - webServer: { - command: 'npm run start', - url: 'http://127.0.0.1:9876', - reuseExistingServer: true - }, -}); diff --git a/src/config/db.ts b/src/config/db.ts index edfe3832..5ed4d6a0 100644 --- a/src/config/db.ts +++ b/src/config/db.ts @@ -14,7 +14,7 @@ const db: sqlite3.Database = new sqlite3.Database(dbPath, (error: unknown) => { timestamp DATETIME DEFAULT CURRENT_TIMESTAMP )`, () => { - logger.info("Database created / opened successfully, table is ready."); + logger.info("Database created / checked successfully, table is ready."); }, ); } diff --git a/src/config/hostsystem.ts b/src/config/hostsystem.ts index 0af379f6..87928a8e 100644 --- a/src/config/hostsystem.ts +++ b/src/config/hostsystem.ts @@ -3,7 +3,8 @@ import { VERSION, HA_MASTER, HA_UNSAFE, - TRUSTED_PROXYS, + TRUSTED_PROXIES, + LOG_LEVEL, } from "./variables"; import fs from "fs"; import logger from "../utils/logger"; @@ -16,7 +17,15 @@ const version: string = VERSION || "unknown"; const masterNode: string = HA_MASTER === "true" ? "✓" : "✗"; const unsafeSync: string = HA_UNSAFE === "true" ? "✓" : "✗"; -function writeUserConf() { +let trustedProxies: string = ""; + +if (TRUSTED_PROXIES) { + trustedProxies = TRUSTED_PROXIES; +} else { + trustedProxies = "✗"; +} + +function writeUserConf(port: number) { let previousConfig = null; let shouldRewriteConfig = false; @@ -64,6 +73,7 @@ function writeUserConf() { logger.info("-----------------------------------------"); logger.info(`Starting at : ${startDetails.startedAt}`); + logger.info(`Running env : ${process.env.NODE_ENV}`); logger.info(`Version : ${startDetails.backendVersion}`); logger.info(`Docker : ${installationDetails.inDocker}`); logger.info(`Running as : ${installationDetails.installedBy}`); @@ -71,7 +81,12 @@ function writeUserConf() { logger.info(`Arch : ${installationDetails.arch}`); logger.info(`Master node : ${masterNode}`); logger.info(`Unsafe sync : ${unsafeSync}`); - logger.info(`Proxies : ${TRUSTED_PROXYS}`); + logger.info(`Proxies : ${trustedProxies}`); + logger.info(`Log Level : ${LOG_LEVEL}`); + logger.info(`Server : http://localhost:${port}`); + if (process.env.NODE_ENV !== "production") { + logger.info(`Swagger-UI : http://localhost:${port}/api-docs`); + } logger.info("-----------------------------------------"); } diff --git a/src/config/initFiles.ts b/src/config/initFiles.ts index 008749cb..7524907c 100644 --- a/src/config/initFiles.ts +++ b/src/config/initFiles.ts @@ -3,6 +3,7 @@ import logger from "../utils/logger"; import { atomicWrite } from "../utils/atomicWrite"; const files = [ + { path: "./src/data/highAvailability.json", content: "{}" }, { path: "./src/data/password.json", content: JSON.stringify( diff --git a/src/config/stacks.ts b/src/config/stacks.ts new file mode 100644 index 00000000..def75dcb --- /dev/null +++ b/src/config/stacks.ts @@ -0,0 +1,260 @@ +import logger from "../utils/logger"; +import fs from "fs"; +import path from "path"; +import YAML from "yamljs"; +import { DockerComposeFile } from "../typings/dockerCompose"; +import { dockerStackProperty, dockerStackEnv } from "../typings/dockerStackEnv"; +import { stackConfig } from "../typings/stackConfig"; +import { validate } from "../handlers/stack"; +import { atomicWrite } from "../utils/atomicWrite"; +import { AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT } from "./variables"; + +const nameRegex = /^[A-Za-z0-9_-]+$/; +const stackRootFolder = "./stacks"; +const configFilePath = `${stackRootFolder}/.config.json`; + +async function getStackCompose(name: string) { + try { + await validate(name); + const stackCompose = `${stackRootFolder}/${name}/docker-compose.yaml`; + + return YAML.parse(fs.readFileSync(stackCompose, "utf-8")); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +async function getStackConfig(): Promise { + try { + return fs.readFileSync(configFilePath, "utf-8"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +async function createStack( + name: string, + content: DockerComposeFile, + override: boolean, +) { + try { + if (!name) { + const errorMsg = "Name required"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + if (!nameRegex.test(name)) { + const errorMsg = "Name does not match [A-Za-z0-9_-]"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + if (!content) { + const errorMsg = "Data for this stack is required"; + logger.error(errorMsg); + throw new Error(errorMsg); + } + + const stackFolderPath = `${stackRootFolder}/${name}`; + + if (!fs.existsSync(stackFolderPath)) { + fs.mkdirSync(stackFolderPath, { recursive: true }); + logger.debug(`Created stack folder at ${stackFolderPath}`); + } + + updateConfigFile(name); + + let yamlContent = ""; + let environmentFileData: dockerStackEnv = { environment: [] }; + if (AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT == "true" && override == false) { + logger.debug("AEFM is activated"); + const { cleanCompose, envSchema } = extractAndRemoveEnv(content); + yamlContent = YAML.stringify(cleanCompose, 10, 2); + environmentFileData = envSchema; + + await writeEnvFile(name, environmentFileData); + } else { + yamlContent = YAML.stringify(content, 10, 2); + } + + const filePath = `${stackFolderPath}/docker-compose.yaml`; + atomicWrite(filePath, yamlContent); + logger.debug(`Stack content written to ${filePath}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +function updateConfigFile(stackName: string) { + try { + let config: stackConfig = { stacks: [] }; + if (fs.existsSync(configFilePath)) { + const configData = fs.readFileSync(configFilePath, "utf-8"); + config = JSON.parse(configData); + } + + const stacks = config.stacks || []; + + if (!stacks.includes(stackName)) { + stacks.push(stackName); + } + + const updatedConfig = { stacks }; + atomicWrite(configFilePath, JSON.stringify(updatedConfig, null, 2)); + logger.debug(`Updated .config.json with stack name: ${stackName}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(`Error updating .config.json: ${errorMsg}`); + throw new Error(errorMsg); + } +} + +async function writeEnvFile( + name: string, + data: dockerStackEnv, +): Promise { + try { + await validate(name); + + if (!nameRegex.test(name)) { + const sanitizedStackName = name.replace(/\n|\r/g, ""); + const errorMsg = `Invalid stack name: ${sanitizedStackName}`; + logger.error(errorMsg); + return false; + } + + const dockerEnvPath = path.resolve(stackRootFolder, name, "docker.env"); + const dockerEnvPathBak = path.resolve( + stackRootFolder, + name, + ".docker.env.bak", + ); + + if ( + !dockerEnvPath.startsWith(path.resolve(stackRootFolder)) || + !dockerEnvPathBak.startsWith(path.resolve(stackRootFolder)) + ) { + const sanitizedStackName = name.replace(/\n|\r/g, ""); + const errorMsg = `Path traversal attempt detected: ${sanitizedStackName}`; + logger.error(errorMsg); + return false; + } + + const variableNames = data.environment.map(({ name }) => name); + const duplicateVars = variableNames.filter( + (item, index) => variableNames.indexOf(item) !== index, + ); + + if (duplicateVars.length > 0) { + const duplicatesList = duplicateVars.join(", "); + const sanitizedDuplicatesList = duplicatesList.replace(/\n|\r/g, ""); + const errorMsg = `Duplicate environment variables detected: ${sanitizedDuplicatesList}`; + logger.error(errorMsg); + return false; + } + + const envFileContent = data.environment + .map(({ name, value }) => `${name}="${value}"`) + .join("\n"); + + if (fs.existsSync(dockerEnvPath)) { + logger.debug("Creating a local backup"); + const previousData = fs.readFileSync(dockerEnvPath); + atomicWrite(dockerEnvPathBak, previousData); + } + + atomicWrite(dockerEnvPath, envFileContent); + return true; + } catch (error: unknown) { + const errorMsg = ( + error instanceof Error ? error.message : String(error) + ).replace(/\n|\r/g, ""); + logger.error(errorMsg); + throw new Error(errorMsg); + } +} + +async function getEnvFile(name: string) { + await validate(name); + const dockerEnvPath = path.resolve(stackRootFolder, name, "docker.env"); + if (!dockerEnvPath.startsWith(path.resolve(stackRootFolder))) { + throw new Error("Invalid path"); + } + + if (fs.existsSync(dockerEnvPath)) { + const data = fs.readFileSync(dockerEnvPath, "utf-8"); + + const environment: dockerStackProperty[] = data + .split("\n") + .filter((line) => line.trim() !== "" && line.includes("=")) + .map((line) => { + const [name, ...valueParts] = line.split("="); + const value = valueParts.join("=").replace(/^"|"$/g, ""); + return { name: name.trim(), value: value.trim() }; + }); + + return { environment }; + } else { + return null; + } +} + +function extractAndRemoveEnv(data: DockerComposeFile): { + cleanCompose: DockerComposeFile; + envSchema: dockerStackEnv; +} { + const environment: dockerStackProperty[] = []; + const envCount: Record = {}; + + for (const [, service] of Object.entries(data.services)) { + if (service.environment) { + for (const key of Object.keys(service.environment)) { + envCount[key] = (envCount[key] || 0) + 1; + } + } + } + + for (const [, service] of Object.entries(data.services)) { + if (service.environment) { + const remainingEnvironment: Record = {}; + + for (const [key, value] of Object.entries(service.environment)) { + if (envCount[key] === 1) { + environment.push({ name: key, value }); + } else { + remainingEnvironment[key] = value; + } + } + + service.environment = remainingEnvironment; + + if (Object.keys(service.environment).length === 0) { + delete service.environment; + } + } + + if (!service.env_file) { + service.env_file = ["./docker.env"]; + } + } + + return { + cleanCompose: data, + envSchema: { environment }, + }; +} + +export { + createStack, + getStackConfig, + getStackCompose, + writeEnvFile, + getEnvFile, +}; diff --git a/src/config/swagger.yaml b/src/config/swagger.yaml new file mode 100644 index 00000000..9a1d50fb --- /dev/null +++ b/src/config/swagger.yaml @@ -0,0 +1,2095 @@ +openapi: "3.0.0" + +security: + - passwordAuth: [] + +info: + title: "DockStatAPI" + version: "2.0.1" + externalDocs: + description: DockStat(API) Wiki + url: https://outline.itsnik.de/s/dockstat + license: + name: BSD-3-Clause + url: https://github.com/Its4Nik/dockstatapi/tree/main?tab=BSD-3-Clause-1-ov-file#readme + contact: + email: info@itsnik.de + description: |- + ![DockStat](https://github.com/Its4Nik/dockstatapi/blob/dev/.github/DockStat-dark.png?raw=true) + + # Pipelines + + [![Docker Image CI](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/build-image.yml?branch=main&label=Docker%20Image%20CI&style=for-the-badge&logo=docker)](https://github.com/Its4Nik/dockstatapi/actions/workflows/build-image.yml) + [![Validation](https://img.shields.io/github/actions/workflow/status/Its4Nik/dockstatapi/validation.yml?branch=dev&label=Validation&style=for-the-badge&logo=checkmarx)](https://github.com/Its4Nik/dockstatapi/actions/workflows/validation.yml) + + # Feature List: + + - Swagger API Documentation + - Database (Keeps data for 24 hours max) + - Advanced authentication using hashes and salt + - `http` API to configure the backend + - Multi-arch docker builds (using buildx github action) + - Advanced security through middlewares: rate-limiting and authentication + - Multi Arch Docker builds through docker buildx + - High Availability using single master and unlimited worker nodes! + +

+ Your container graph + [Interactive Graph](http://localhost:9876/graph) + + [Raw image](http://localhost:9876/graph/image) + + --- + + ![Your container graph](http://localhost:9876/graph/image) +
+ + # 🔗 DockStatAPI v2 Documentation + + _⚠️ = Deprecation warning_ + + - [Introduction](https://outline.itsnik.de/s/dockstat) + + - [DockstatAPI v2](https://outline.itsnik.de/s/dockstat/doc/dockstatapi-v2-XRMDKRqMIg) + + - [API reference](https://outline.itsnik.de/s/dockstat/doc/api-reference-1PTxqx1MQ6) + - [How dependency graphs are made](https://outline.itsnik.de/s/dockstat/doc/how-the-dependecy-graphs-are-made-svuZbEHH9g) + + - [DockStat v1](https://outline.itsnik.de/s/dockstat/doc/dockstat-v1-zVaFS4zROI) + + - [⚠️ Customisation](https://outline.itsnik.de/s/dockstat/doc/customization-PiBz4OpQIZ) + - [⚠️ Themes](https://outline.itsnik.de/s/dockstat/doc/themes-BFhN6ZBbYx) + - [⚠️ Installation](https://outline.itsnik.de/s/dockstat/doc/installation-DaO99bB86q) + + - [⚠️ DockStatAPI v1](https://outline.itsnik.de/s/dockstat/doc/dockstatapi-v1-jLcVCfPNmS) + - [⚠️ Integrations](https://outline.itsnik.de/s/dockstat/doc/integrations-Agq1oL6HxF) + - [⚠️ Backend API reference](https://outline.itsnik.de/s/dockstat/doc/backend-api-reference-YzcBbDvY33) + +tags: + - name: Authentication + description: Routes to setup / configure authentication + + - name: Configuration + description: Configuring the backend + + - name: Database queries + description: Queries made against the SQLite database + + - name: "Frontend Configuration" + description: Backend routes to configure the integrated "frontend service" + + - name: Miscellaneous + description: Some "random" routes which still can be useful + + - name: High availability + description: High availability routes, mainly used by HA sync + + - name: Notification Service + description: Routes to configure the notification service + + - name: Stacks + description: Management of the Stack module + +servers: + - url: http://localhost:9876 + description: "Your DockStatAPI instance" + +paths: + # ------------------------------ + # Authentication setup: + /auth/enable: + post: + tags: + - "Authentication" + summary: Enable authentication for every route + operationId: enableAuth + parameters: + - name: password + in: query + required: true + explode: true + schema: + type: string + default: super-secret + responses: + "200": + description: Success - Successfully enabled authentication + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Authentication enabled successfully" + + "403": + description: Error - Password is required / Authentication is already enabled + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /auth/disable: + post: + tags: + - "Authentication" + summary: Disable authentication for every route + operationId: disableAuth + parameters: + - name: password + in: query + required: true + explode: true + schema: + type: string + default: super-secret + responses: + "200": + description: Succes - Succesfully disabled authentication + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Authentication disabled successfully" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + # ------------------------------ + # Database queries: + /data/latest: + get: + tags: + - "Database queries" + summary: Fetched the last added entry from the Database and provides it via a JSON output + operationId: getLatestData + responses: + "200": + description: Succes - Successfully fetched the database + content: + application/json: + schema: + $ref: "#/components/schemas/ServerContainers" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "404": + description: Error - No entries found inside database + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /data/all: + get: + tags: + - "Database queries" + summary: Provides all database entries with an index starting from 0 + operationId: getAllData + responses: + "200": + description: Succes - Successfully fetched the database + content: + application/json: + schema: + $ref: "#/components/schemas/IndexedServerContainers" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "404": + description: Error - No entries found inside database + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /data/clear: + delete: + tags: + - "Database queries" + summary: Deletes all database entries + operationId: dataClear + responses: + "200": + description: Succes - Successfully cleared the database + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Successfully cleared the database" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + # ------------------------------ + # Configuration: + /api/hosts: + get: + tags: + - "Configuration" + summary: Retrieves the configured name of all added Hosts + operationId: getHosts + responses: + "200": + description: Succes - Successfully fetched all configured hosts + content: + application/json: + schema: + type: array + example: '[ "Host-1", "Host-2" ]' + + "400": + description: Error - No hosts defined, please add a host via /conf/addHost + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/host/{hostName}/stats: + get: + tags: + - "Configuration" + summary: Shows general information about the target host, like dockeer engine version + operationId: getHostInfo + parameters: + - name: hostName + in: path + description: Hostname of the target host + required: true + schema: + type: string + responses: + "200": + description: Succes - Successfully fetched info about target host + content: + application/json: + schema: + $ref: "#/components/schemas/HostInfo" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "404": + description: Error - No Host found + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/system: + get: + tags: + - "Configuration" + summary: Fetched the installation details of this DockStatAPI instance + operationId: getSystem + responses: + "200": + description: Succes - Fetched system configuration + content: + application/json: + schema: + type: object + properties: + installedAt: + type: string + format: date-time + example: "2024-12-25T19:20:02.418Z" + backendVersion: + type: string + example: "2.0.1" + inDocker: + type: boolean + example: false + installedBy: + type: string + example: "user" + platform: + type: string + example: "linux" + arch: + type: string + example: "x64" + "400": + description: Error - Received empty configuration + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/config: + get: + tags: + - "Configuration" + summary: Retrieves information about the configured hosts + operationId: getConfig + responses: + "200": + description: Succes - Fetched system configuration + content: + application/json: + schema: + type: object + properties: + hosts: + type: array + items: + type: object + properties: + name: + type: string + example: "Host-1" + url: + type: string + example: "192.168.2.12" + port: + type: string + example: "2375" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/frontend-config: + get: + tags: + - "Configuration" + summary: Fetches the "Frontend Configuration" => Used in the DockStat frontend + operationId: getFrontendConfig + responses: + "200": + description: Succes - Fetched "Frontend Configuration" + content: + application/json: + schema: + $ref: "#/components/schemas/FrontendConfig" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/current-schedule: + get: + tags: + - "Configuration" + summary: Shows the current configured schedule (for fetching data) in seconds + operationId: getSchedule + responses: + "200": + description: Succes - Fetched schedule + content: + application/json: + schema: + type: object + properties: + interval: + type: integer + example: 600 + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/status: + get: + tags: + - "Miscellaneous" + summary: Pings all hosts to check reachability + operationId: getStatus + responses: + "200": + description: Succes - Gathered Status + content: + application/json: + schema: + $ref: "#/components/schemas/ApiStatus" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /api/containers: + get: + tags: + - "Miscellaneous" + summary: Fetched all container data directly from the host without reading from the database + operationId: getContainers + responses: + "200": + description: Succes - Fetched all container statistics + content: + application/json: + schema: + $ref: "#/components/schemas/ServerContainers" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + # ------------------------------ + # High availability: + /ha/config: + get: + tags: + - "High availability" + summary: Get the current high availability config + operationId: getHaConfig + responses: + "200": + description: Succes - Fetched high availability config + content: + application/json: + schema: + $ref: "#/components/schemas/HaConfig" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + + /ha/sync: + post: + tags: + - "High availability" + deprecated: true + summary: This route is not deprecated, but only used by the high availability feature + operationId: syncHa + responses: + "200": + description: Succes - Synchronized successfully + "400": + description: Error - `files` object is missing or invalid + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + + /ha/prepare-sync: + get: + tags: + - "High availability" + deprecated: true + summary: This route is not deprecated, but only used by the high availability feature + operationId: syncPrepare + responses: + "200": + description: Succes - Prepared all files for syncing + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + + # ------------------------------ + # Notification Service: + /notification-service/get-template: + get: + tags: + - "Notification Service" + summary: Fetches the current template for the notification service + operationId: getNsTemplate + responses: + "200": + description: Success - Fetched notification template + content: + application/json: + schema: + $ref: "#/components/schemas/Notification-Template" + "400": + description: Error - Error while reading file (see server logs) + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /notification-service/set-template: + post: + tags: + - "Notification Service" + - "Configuration" + summary: Update the current notification template + operationId: setNsTemplate + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Notification-Template" + responses: + "200": + description: Success - Template updated successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Template updated successfully." + "400": + description: Error - Invalid input format. Expected JSON with a 'text' field + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Invalid input format. Expected JSON with a 'text' field" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /notification-service/test/{type}/{containerId}: + post: + tags: + - "Notification Service" + summary: Test a specific type of notification using real data + operationId: testNs + parameters: + - in: path + name: type + required: true + schema: + type: string + description: The desired notification to test + + - in: path + name: containerId + required: true + schema: + type: string + description: A real container ID is needed to test templating functionality + responses: + "200": + description: Success - Sent test notification + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Sent test notification" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + # ------------------------------ + # Configuration: + /conf/addHost: + put: + tags: + - "Configuration" + summary: Adds a new host to the configuration and starts querying it + operationId: addHost + parameters: + - name: name + in: query + required: true + description: A name for the new host + - name: url + in: query + required: true + description: The target IP or dns entry + - name: port + in: query + required: true + description: The targets port on which Docker-Socket-Proxy runs + responses: + "200": + description: Success - Host added successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Host added successfully" + "400": + description: Error - Name, Port, and URL are required + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Name, Port, and URL are required" + "401": + description: Host already exists + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Host already exists" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /conf/removeHost: + delete: + tags: + - "Configuration" + summary: Removes an host from the config + operationId: removeHost + parameters: + - name: hostName + in: query + required: true + description: "The name of the to-be-removed-Host" + responses: + "200": + description: Success - Host removed successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Host removed successfully" + "401": + description: Error - Host name is required + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Host name is required" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "404": + description: Error - Host not found + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Host not found" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /conf/scheduler: + tags: + - "Configuration" + summary: Adjust the scheduler timing + operationId: adjustSchedule + parameters: + - name: interval + in: query + required: true + description: "Adjust the schedule timing (in seconds)" + responses: + "200": + description: Success - Timing updated + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Updated interval" + "401": + description: Error - Interval must be between 5 minutes and 6 hours + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Interval must be between 5 minutes and 6 hours." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + # ------------------------------ + # Frontend routes: + /frontend/show/{containerName}: + post: + tags: + - "Frontend Configuration" + operationId: frShowCon + summary: Set `hide` to false for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to unhide + responses: + "200": + description: Success - now showing the container + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Container unhidden successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/hide/{containerName}: + delete: + tags: + - "Frontend Configuration" + operationId: frHideCon + summary: Set `hide` to true for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to unhide + responses: + "200": + description: Success - now hiding the container + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Hid container succesfully" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/tag/{containerName}/{tag}: + post: + tags: + - "Frontend Configuration" + operationId: frTagCon + summary: Add a tag to the tag array for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to add a tag to + - name: tag + in: path + schema: + type: string + required: true + description: The name of the tag to add + responses: + "200": + description: Success - Tag added successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Tag added successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/remove-tag/{containerName}/{tag}: + delete: + tags: + - "Frontend Configuration" + operationId: frRmTagCon + summary: Remove the specified tag from the tag array for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to remove a tag from + - name: tag + in: path + schema: + type: string + required: true + description: The name of the tag to remove + responses: + "200": + description: Success - Tag removed successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Tag removed successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/pin/{containerName}: + post: + tags: + - "Frontend Configuration" + operationId: frPinCon + summary: Set `pinned` to true for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to pin + responses: + "200": + description: Success - Container pinned successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Container pinned successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/unpin/{containerName}: + delete: + tags: + - "Frontend Configuration" + operationId: frRmPinCon + summary: Set `pinned` to false for the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to unpin + responses: + "200": + description: Success - Container unpinned successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Container unpinned successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/add-link/{containerName}/{link}: + post: + tags: + - "Frontend Configuration" + operationId: frAddLinkCon + summary: Add a link to the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to add a link to + - name: link + in: path + schema: + type: URI + required: true + allowReserved: false + description: The URI of the link (please use Uniform Resource Identifier format) + responses: + "200": + description: Success - Link added to container successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Link added successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/remove-link/{containerName}: + delete: + tags: + - "Frontend Configuration" + operationId: frRmLinkCon + summary: Remove a link to the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to remove a link from + responses: + "200": + description: Success - Link removed from container successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Link removed successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/add-icon/{containerName}/{icon}/{useCustomIcon}: + post: + tags: + - "Frontend Configuration" + operationId: frAddIcon + summary: Add an icon (path) to the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to add an icon to + - name: icon + in: path + schema: + type: string + required: true + description: The name of the icon file + - name: useCustomIcon + in: path + schema: + type: boolean + required: false + description: If the icon is a custom icon or not + responses: + "200": + description: Success - Icon added to container successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Icon added successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /frontend/remove-icon/{containerName}: + delete: + tags: + - "Frontend Configuration" + operationId: frRmIcon + summary: Remove an icon from the specified container + parameters: + - name: containerName + in: path + schema: + type: string + required: true + description: The name of the container to remove an icon from + responses: + "200": + description: Success - Icon removed from container successfully + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Icon removed successfully." + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + # ------------------------------ + # Stack management + /stacks/create/{name}: + post: + tags: + - "Stacks" + operationId: createStack + summary: Creates a docker-compose file inside the stack name directory + requestBody: + required: true + content: + application/json: + schema: + type: string + description: Your docker-compose.yaml contents + parameters: + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack created + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Stack created" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /stacks/start/{name}: + post: + tags: + - "Stacks" + operationId: startStack + summary: Starts the defined stack + parameters: + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack started + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Stack created" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /stacks/stop/{name}: + post: + tags: + - "Stacks" + operationId: stopStack + summary: Stops the defined stack + parameters: + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack stopped + content: + application/json: + schema: + type: object + properties: + status: + type: string + example: "success" + message: + type: string + example: "Stack stopped" + + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /stacks/get/{name}: + get: + tags: + - "Stacks" + operationId: getStack + summary: Get the docker-compose.yaml (as JSON) from the defined stack + parameters: + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack fetched + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /stacks/set-env/{name}: + post: + tags: + - "Stacks" + operationId: setStackEnv + summary: Set the docker.env (as JSON) from the defined stack + requestBody: + required: true + content: + application/json: + schema: + type: string + description: Your docker.env contents + parameters: + - name: override + in: query + required: false + description: Whether to override (true) the automatic environment file management (boolean value) + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack environment set + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + + /stacks/get-env/{name}: + get: + tags: + - "Stacks" + operationId: getStackEnv + summary: Get the docker.env (as JSON) from the defined stack + parameters: + - name: name + in: path + schema: + type: string + required: true + description: The name of the stack + responses: + "200": + description: Success - Stack config fetched + "403": + description: Error - Password is required + content: + application/json: + schema: + $ref: "#/components/schemas/403" + "500": + description: Error - Critical Error, please see the server's logs + content: + application/json: + schema: + $ref: "#/components/schemas/500" + "503": + description: Error - The high-availability lock is currently active, please try again later + content: + application/json: + schema: + $ref: "#/components/schemas/503" + +# ------------------------------ +components: + securitySchemes: + passwordAuth: + type: apiKey + in: header + name: x-password + description: Password required for authentication + + schemas: + Notification-Template: + type: object + properties: + text: + type: string + example: "{{container}} on {{host}} is {{state}}" + + IndexedServerContainers: + type: object + properties: + "0": + type: object + properties: + Host-1: + type: array + items: + $ref: "#/components/schemas/Container" + additionalProperties: false + + ServerContainers: + type: object + properties: + Host-1: + type: array + items: + $ref: "#/components/schemas/Container" + additionalProperties: false + + Container: + type: object + properties: + name: + type: string + description: The name of the container. + example: "Container-1" + id: + type: string + description: The unique identifier of the container. + example: "a84ca83bb0e7f8c24fe472b9164d40a4bae518ece8369e6776f722b81dd65bcf" + hostName: + type: string + description: The hostname of the server. + example: "Host-1" + state: + type: string + description: The current state of the container. + example: "running" + cpu_usage: + type: number + description: The CPU usage of the container in arbitrary units. + example: 625185.1851851852 + mem_usage: + type: integer + description: Memory usage in bytes. + example: 359899136 + mem_limit: + type: integer + description: Memory limit in bytes. + example: 8127893504 + net_rx: + type: integer + description: Total network received in bytes. + example: 11004185462 + net_tx: + type: integer + description: Total network transmitted in bytes. + example: 9950013623 + current_net_rx: + type: integer + description: Current network received in bytes. + example: 11004185462 + current_net_tx: + type: integer + description: Current network transmitted in bytes. + example: 9950013623 + networkMode: + type: string + description: The network mode of the container. + example: "docker_default" + + HostInfo: + type: object + properties: + hostName: + type: string + example: "Host-1" + info: + type: object + properties: + ID: + type: string + format: uuid + example: "32b5fad9-9b12-48b0-9ce7-178f2886ad60" + Containers: + type: integer + example: 8 + ContainersRunning: + type: integer + example: 8 + ContainersPaused: + type: integer + example: 0 + ContainersStopped: + type: integer + example: 0 + Images: + type: integer + example: 7 + OperatingSystem: + type: string + example: "Ubuntu 24.04 LTS" + KernelVersion: + type: string + example: "6.8.0-38-generic" + Architecture: + type: string + example: "x86_64" + MemTotal: + type: integer + example: 8127893504 + NCPU: + type: integer + example: 4 + version: + type: object + properties: + Components: + type: object + properties: + Engine: + type: string + example: "27.1.1" + containerd: + type: string + example: "1.7.19" + runc: + type: string + example: "1.7.19" + docker-init: + type: string + example: "0.19.0" + + Frontend: + type: object + properties: + name: + type: string + description: The name of the container + hidden: + type: boolean + description: Whether the container is hidden + tags: + type: array + items: + type: string + description: List of tags associated with the container + link: + type: string + format: uri + description: A link associated with the container + icon: + type: string + description: Icon for the container + pinned: + type: boolean + description: Whether the container is pinned + required: + - name + + FrontendConfig: + type: array + items: + $ref: "#/components/schemas/Frontend" + + ApiStatus: + type: object + properties: + ApiReachable: + type: boolean + description: Whether the API is reachable + online: + type: object + description: Status of individual services keyed by their names + properties: + Host-1: + type: boolean + Host-2: + type: boolean + required: + - ApiReachable + - online + + HaConfig: + type: object + properties: + active: + type: boolean + description: Whether High availability is active or nots + master: + type: boolean + description: Whether this node is the master node + nodes: + type: array + items: + type: string + format: hostname + description: List of nodes in the cluster, specified by hostname or IP with port + required: + - active + - master + - nodes + + 401: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Invalid password" + + 403: + type: object + properties: + status: + type: string + example: "denied" + message: + type: string + example: "Password required" + + 500: + type: object + properties: + status: + type: string + example: "critical" + message: + type: string + example: "Please see the server logs for more info" + + 503: + type: object + properties: + status: + type: string + example: "error" + message: + type: string + example: "Service unavailable. The high-availability lock is currently active. Please try again later." diff --git a/src/config/swaggerConfig.ts b/src/config/swaggerConfig.ts index cab967f8..39c074a6 100644 --- a/src/config/swaggerConfig.ts +++ b/src/config/swaggerConfig.ts @@ -1,53 +1,10 @@ -const options: { - definition: { - failOnErrors: boolean; - openapi: string; - info: { - title: string; - version: string; - description: string; - }; - components: { - securitySchemes: { - passwordAuth: { - type: string; - in: string; - name: string; - description: string; - }; - }; - }; - security: Array<{ - passwordAuth: unknown[]; - }>; - }; - apis: string[]; -} = { - definition: { - failOnErrors: true, - openapi: "3.0.0", - info: { - title: "DockStatAPI", - version: "2", - description: "An API used to query muliple docker hosts", - }, - components: { - securitySchemes: { - passwordAuth: { - type: "apiKey", - in: "header", - name: "x-password", - description: "Password required for authentication", - }, - }, - }, - security: [ - { - passwordAuth: [], - }, - ], +import { SwaggerOptions } from "swagger-ui-express"; +import { css } from "./swaggerTheme"; + +export const options: SwaggerOptions = { + swaggerOptions: { + tryItOutEnabled: true, }, - apis: ["./src/routes/*/*.ts"], + customCss: css, + explorer: false, }; - -export default options; diff --git a/src/config/swaggerTheme.ts b/src/config/swaggerTheme.ts new file mode 100644 index 00000000..d8a879c9 --- /dev/null +++ b/src/config/swaggerTheme.ts @@ -0,0 +1,6 @@ +export const css = ` + +.swagger-ui .topbar { + display: none +} +`; diff --git a/src/config/variables.ts b/src/config/variables.ts index 26a522be..37c67a23 100644 --- a/src/config/variables.ts +++ b/src/config/variables.ts @@ -3,7 +3,7 @@ import vars from "../data/variables.json"; export const { VERSION, RUNNING_IN_DOCKER, - TRUSTED_PROXYS, + TRUSTED_PROXIES, HA_MASTER, HA_MASTER_IP, HA_NODE, @@ -21,4 +21,6 @@ export const { TELEGRAM_CHAT_ID, WHATSAPP_API_URL, WHATSAPP_RECIPIENT, + AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT, + LOG_LEVEL, } = vars; diff --git a/src/controllers/containerController.ts b/src/controllers/containerController.ts index 61745e17..2883dad9 100644 --- a/src/controllers/containerController.ts +++ b/src/controllers/containerController.ts @@ -1,4 +1,4 @@ -import getDockerClient from "../utils/dockerClient"; +import { getDockerClient } from "../utils/dockerClient"; import logger from "../utils/logger"; import { Request, Response } from "express"; import { createResponseHandler } from "../handlers/response"; diff --git a/src/controllers/fetchData.ts b/src/controllers/fetchData.ts index 07438ec2..06e52a93 100644 --- a/src/controllers/fetchData.ts +++ b/src/controllers/fetchData.ts @@ -1,5 +1,5 @@ import db from "../config/db"; -import fetchAllContainers from "../utils/containerService"; +import { fetchAllContainers } from "../utils/containerService"; import logger from "../utils/logger"; import fs from "fs"; import { atomicWrite } from "../utils/atomicWrite"; @@ -68,9 +68,8 @@ const fetchData = async (): Promise => { logger.info("No state change detected, notifications not triggered."); } } catch (error: unknown) { - logger.error( - `Error fetching data: ${JSON.stringify(error)} \nStack trace: ${(error as Error).stack}`, - ); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } }; diff --git a/src/controllers/frontendConfiguration.ts b/src/controllers/frontendConfiguration.ts index e8e035c1..ed4e59dd 100644 --- a/src/controllers/frontendConfiguration.ts +++ b/src/controllers/frontendConfiguration.ts @@ -23,8 +23,8 @@ async function hideContainer(containerName: string) { await saveData(data); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -41,8 +41,8 @@ async function unhideContainer(containerName: string) { cleanupData(); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -66,8 +66,8 @@ async function addTagToContainer(containerName: string, tag: string) { await saveData(data); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -86,8 +86,8 @@ async function removeTagFromContainer(containerName: string, tag: string) { cleanupData(); } } catch (error: unknown) { - logger.error(error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -108,8 +108,8 @@ async function pinContainer(containerName: string) { await saveData(data); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -126,8 +126,8 @@ async function unpinContainer(containerName: string) { cleanupData(); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -149,8 +149,8 @@ async function setLink(containerName: string, link: string) { await saveData(data); } } catch (error: unknown) { - logger.error(error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } else { logger.error(`Provided link is not valid: ${link}`); @@ -171,8 +171,8 @@ async function removeLink(containerName: string) { cleanupData(); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -201,8 +201,8 @@ async function setIcon(containerName: string, icon: string, custom: boolean) { await saveData(data); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -219,8 +219,8 @@ async function removeIcon(containerName: string) { cleanupData(); } } catch (error: unknown) { - logger.error(error as Error); - throw new Error(error as string); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -252,7 +252,8 @@ async function saveData(data: FrontendConfig) { ); logger.info("Succesfully wrote to file"); } catch (error: unknown) { - logger.error(error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -277,7 +278,8 @@ async function cleanupData() { await saveData(cleanedData); } catch (error: unknown) { - logger.error(error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } diff --git a/src/controllers/highAvailability.ts b/src/controllers/highAvailability.ts index 3e61b16f..45db9d7b 100644 --- a/src/controllers/highAvailability.ts +++ b/src/controllers/highAvailability.ts @@ -57,9 +57,9 @@ async function acquireLock(): Promise { try { atomicWrite(lockFilePath, "locked", { exclusive: true }); logger.debug("Lock acquired."); - } catch (error) { - logger.error(`Error acquiring lock: ${(error as Error).message}`); - throw new Error("Failed to acquire lock."); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -69,8 +69,9 @@ async function releaseLock(): Promise { await fs.promises.unlink(lockFilePath); logger.debug("Lock released."); } - } catch (error) { - logger.error(`Error releasing lock: ${(error as Error).message}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -88,8 +89,9 @@ async function writeConfig( await fs.promises.writeFile(filePath, jsonData); logger.debug(`${filePath} has been written.`); - } catch (error) { - logger.error(`Error writing config: ${(error as Error).message}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } finally { await releaseLock(); } @@ -104,7 +106,8 @@ async function readConfig(): Promise { ); return data; } catch (error: unknown) { - logger.error(`Error reading HA-Config: ${(error as Error).message}`); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); return null; } finally { await releaseLock(); @@ -118,8 +121,9 @@ async function prepareFilesForSync(): Promise> { const content = await fs.promises.readFile(filePath, "utf-8"); fileData[filePath] = content; } - } catch (error) { - logger.error(`Error preparing files for sync: ${(error as Error).message}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } return fileData; } @@ -147,8 +151,9 @@ async function checkApiReachable(node: string): Promise { logger.error(`Node ${node} is not reachable. ApiReachable: false`); return false; } - } catch (error) { - logger.error(`Error reaching node ${node}: ${(error as Error).message}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); return false; } } @@ -229,7 +234,7 @@ async function startMasterNode() { ? HA_NODE.split(",").reduce((cache, node, index) => { const [ip, port] = node.trim().split(":"); if (ip && port) { - cache[`node-${index + 1}`] = { ip, id: parseInt(port, 10) }; + cache[`node-${index + 1}`] = { ip, port: parseInt(port, 10) }; } return cache; }, {} as NodeCache) @@ -260,10 +265,9 @@ async function ensureFileExists( await fs.promises.mkdir(dirPath, { recursive: true }); await fs.promises.writeFile(filePath, content, { flag: "w" }); logger.info(`File updated: ${filePath}`); - } catch (error) { - logger.error( - `Error creating/updating file ${filePath}: ${(error as Error).message}`, - ); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } finally { await releaseLock(); } diff --git a/src/controllers/proxy.ts b/src/controllers/proxy.ts index 601f1556..c091590a 100644 --- a/src/controllers/proxy.ts +++ b/src/controllers/proxy.ts @@ -1,9 +1,9 @@ import { Application } from "express"; import logger from "../utils/logger"; -import { TRUSTED_PROXYS } from "../config/variables"; +import { TRUSTED_PROXIES } from "../config/variables"; export default function trustedProxies(app: Application) { - const trusted: string = TRUSTED_PROXYS; + const trusted: string = TRUSTED_PROXIES; if (!trusted) { logger.warn( diff --git a/src/controllers/scheduler.ts b/src/controllers/scheduler.ts index caa19481..db450d95 100644 --- a/src/controllers/scheduler.ts +++ b/src/controllers/scheduler.ts @@ -12,7 +12,8 @@ const scheduleFetch = () => { fetchData(); cleanupOldEntries(); } catch (error: unknown) { - logger.error(`Error during scheduled fetch: ${error}`); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } intervalId = setInterval(() => { @@ -81,8 +82,9 @@ const cleanupOldEntries = async () => { try { db.run("DELETE FROM data WHERE timestamp < ?", twentyFourHoursAgo, Error); logger.info("Old entries cleared from the database."); - } catch (Error: unknown) { - logger.error(`Error clearing old entries: ${(Error as Error).message}`); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } }; diff --git a/src/data/frontendConfiguration.json b/src/data/frontendConfiguration.json index fe51488c..0637a088 100644 --- a/src/data/frontendConfiguration.json +++ b/src/data/frontendConfiguration.json @@ -1 +1 @@ -[] +[] \ No newline at end of file diff --git a/src/handlers/api.ts b/src/handlers/api.ts index 6f62c056..fa7f1f7e 100644 --- a/src/handlers/api.ts +++ b/src/handlers/api.ts @@ -1,7 +1,7 @@ import extractRelevantData from "../utils/extractHostData"; import { Request, Response } from "express"; -import getDockerClient from "../utils/dockerClient"; -import fetchAllContainers from "../utils/containerService"; +import { getDockerClient } from "../utils/dockerClient"; +import { fetchAllContainers } from "../utils/containerService"; import { getCurrentSchedule } from "../controllers/scheduler"; import fs from "fs"; import checkReachability from "../utils/connectionChecker"; @@ -62,6 +62,10 @@ class ApiHandler { const version = await docker.version(); const relevantData = extractRelevantData({ hostName, info, version }); + if (!relevantData) { + ResponseHandler.error("No host found", 404); + } + return ResponseHandler.rawData(relevantData, "Fetched Host stats"); } catch (error: unknown) { const errorMsg = error instanceof Error ? error.message : String(error); diff --git a/src/handlers/conf.ts b/src/handlers/conf.ts index e383c4d1..b49dd2a5 100644 --- a/src/handlers/conf.ts +++ b/src/handlers/conf.ts @@ -20,7 +20,7 @@ class ConfHandler { try { const { name, url, port } = req.query as unknown as target; if (!name || !url || !port) { - return ResponseHandler.denied("Name, Port, and URL are required."); + return ResponseHandler.error("Name, Port, and URL are required.", 400); } const config: dockerConfig = JSON.parse( @@ -28,7 +28,7 @@ class ConfHandler { ); if (config.hosts.some((host) => host.name === name)) { - return ResponseHandler.denied("Host already exists."); + return ResponseHandler.error("Host already exists.", 422); } config.hosts.push({ name, url, port }); @@ -47,7 +47,7 @@ class ConfHandler { const hostName = req.query.hostName as string; if (!hostName) { - return ResponseHandler.denied("Host name is required."); + return ResponseHandler.error("Host name is required.", 401); } const currentState = fs.readFileSync(configPath, "utf-8"); @@ -79,8 +79,9 @@ class ConfHandler { const newInterval = parseInterval(interval); if (newInterval < 5 * 60 * 1000 || newInterval > 6 * 60 * 60 * 1000) { - return ResponseHandler.denied( + return ResponseHandler.error( "Interval must be between 5 minutes and 6 hours.", + 401, ); } diff --git a/src/handlers/data.ts b/src/handlers/data.ts index fd3515d6..5d3bf41c 100644 --- a/src/handlers/data.ts +++ b/src/handlers/data.ts @@ -2,6 +2,7 @@ import { Response, Request } from "express"; import db from "../config/db"; import { Table, DataRow } from "../typings/table"; import { createResponseHandler } from "./response"; +import logger from "../utils/logger"; function formatRows(rows: DataRow[]): Record { return rows.reduce( @@ -56,6 +57,35 @@ class DatabaseHandler { ); } + latestRaw(): Promise { + return new Promise((resolve, reject) => { + logger.debug("Reading DB"); + db.get( + "SELECT info FROM data ORDER BY timestamp DESC LIMIT 1", + (error: unknown, row: Partial> | undefined) => { + if (error) { + return reject(`Database query error: ${error}`); + } + + if (!row || !row.info) { + return reject("No data available for /data/latest"); + } + + try { + logger.info("Read latest data"); + const parsedData = JSON.parse(row.info); + logger.debug("Parsed data:", parsedData); + resolve(parsedData); + } catch (error: unknown) { + const errorMsg = + error instanceof Error ? error.message : String(error); + reject(`Error parsing data: ${errorMsg}`); + } + }, + ); + }); + } + all() { const ResponseHandler = createResponseHandler(this.res); const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(); diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts new file mode 100644 index 00000000..53e245f7 --- /dev/null +++ b/src/handlers/graph.ts @@ -0,0 +1,257 @@ +import cytoscape from "cytoscape"; +import logger from "../utils/logger"; +import { AllContainerData, ContainerData } from "./../typings/dockerConfig"; +import { atomicWrite } from "../utils/atomicWrite"; +import { rateLimitedReadFile } from "../utils/rateLimitFS"; + +const CACHE_DIR_JSON = "./src/data/graph.json"; +const CACHE_DIR_HTML = "./src/data/graph.html"; +const _assets = "./src/utils/assets"; +const serverSvg = `${_assets}/server-icon.svg`; +const containerSvg = `${_assets}/container-icon.svg`; +const pngPath = "./src/data/graph.png"; + +async function getPathData(path: string) { + try { + return await rateLimitedReadFile(path); + + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + return false; + } +} + +async function renderGraphToImage( + htmlContent: string, + outputImagePath: string, +): Promise { + let puppeteer; + try { + puppeteer = await import("puppeteer"); + } catch (error) { + logger.error("Puppeteer is not installed. Please install it to generate images."); + throw new Error(`Puppeteer is not installed (${error})`); + } + + let browser; + try { + browser = await puppeteer.default.launch({ + headless: "shell", + args: ["--disable-setuid-sandbox", "--no-sandbox"], + executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, + }); + + const page = await browser.newPage(); + await page.setContent(htmlContent, { waitUntil: "networkidle0" }); + await page.waitForSelector("#cy", { visible: true, timeout: 15000 }); + + await page.waitForFunction( + () => { + const cyElement = document.querySelector("#cy"); + return cyElement ? cyElement.children.length > 0 : false; + }, + { timeout: 10000 } + ); + + await page.screenshot({ + path: outputImagePath, + type: outputImagePath.endsWith(".jpg") ? "jpeg" : "png", + fullPage: true, + captureBeyondViewport: true, + }); + } catch (error: unknown) { + let errorMessage = "Unknown error occurred during browser operation"; + + if (error instanceof Error) { + errorMessage = error.message; + + // Detect common dependency errors + if (errorMessage.includes("libnss3") || errorMessage.includes("libxcb")) { + errorMessage = `❗ Missing system dependencies (libnss3)`; + } + + // Detect Chrome not found errors + if (errorMessage.includes("Failed to launch")) { + errorMessage = `❗ Chrome not found!`; + } + } + + throw new Error(`Graph rendering failed: ${errorMessage}`); + } finally { + if (browser) { + await browser.close().catch(() => { }); + } + } + + logger.info(`Graph rendered and image saved to: ${outputImagePath}`); +} + +async function generateGraphFiles( + allContainerData: AllContainerData, +): Promise { + if (process.env.CI === "true") { + logger.warn("Running inside a CI/CD Action, wont generated graphs"); + return false; + } else { + try { + logger.info("generateGraphFiles >>> Starting generation"); + const graphElements: cytoscape.ElementDefinition[] = []; + + for (const [hostName, containers] of Object.entries(allContainerData)) { + if ("error" in containers) { + // TODO: make error'ed hosts better + graphElements.push({ + data: { + id: hostName, + label: `Host: ${hostName} Error: ${containers.error}`, + type: "server", + }, + }); + } else { + const containerList = containers as ContainerData[]; + + // host node with container count + graphElements.push({ + data: { + id: hostName, + label: `${hostName} - ${containerList.length} Containers`, + type: "server", + }, + }); + + for (const container of containerList) { + // container node + graphElements.push({ + data: { + id: container.id, + label: `${container.name} (${container.state})`, + type: "container", + }, + }); + + // edge between host and container + graphElements.push({ + data: { + source: hostName, + target: container.id, + }, + }); + } + } + } + + atomicWrite(CACHE_DIR_JSON, JSON.stringify(graphElements, null, 2)); + + const htmlContent = ` + + + + + + Cytoscape Graph + + + + +
+ + + + `; + + atomicWrite(CACHE_DIR_HTML, htmlContent); + await renderGraphToImage(htmlContent, pngPath) + .then(() => logger.debug("HTML converted to image successfully!")) + .catch((err) => logger.error("Error:", err)); + + logger.info("generateGraphFiles <<< Files generated successfully"); + return true; + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + return false; + } + } +} + +function getGraphFilePaths() { + return { json: CACHE_DIR_JSON, html: CACHE_DIR_HTML }; +} + +export { generateGraphFiles, getGraphFilePaths }; diff --git a/src/handlers/notification.ts b/src/handlers/notification.ts index ad5c2938..9c10a599 100644 --- a/src/handlers/notification.ts +++ b/src/handlers/notification.ts @@ -27,7 +27,10 @@ class NotificationHandler { if (error) { return ResponseHandler.error(error as string, 400); } - return ResponseHandler.rawData(data, "Fetched notification template"); + return ResponseHandler.rawData( + JSON.parse(data), + "Fetched notification template", + ); }); } catch (error: unknown) { const errorMsg = error instanceof Error ? error.message : String(error); diff --git a/src/handlers/response.ts b/src/handlers/response.ts index 8c6e95b8..ee062102 100644 --- a/src/handlers/response.ts +++ b/src/handlers/response.ts @@ -29,7 +29,7 @@ class ResponseHandler { } critical(log: string) { - logger.error(log); + logger.error(log.replace(/\n|\r/g, "")); this.res.status(500).json({ status: "critical", message: "Please see the server logs for more info", diff --git a/src/handlers/stack.ts b/src/handlers/stack.ts new file mode 100644 index 00000000..e87b533f --- /dev/null +++ b/src/handlers/stack.ts @@ -0,0 +1,162 @@ +import { Response, Request } from "express"; +import { + createStack, + getStackConfig, + getStackCompose, + writeEnvFile, + getEnvFile, +} from "../config/stacks"; +import { DockerComposeFile } from "../typings/dockerCompose"; +import logger from "../utils/logger"; +import * as compose from "docker-compose"; +import { createResponseHandler } from "./response"; +import { stackConfig } from "../typings/stackConfig"; +import { dockerStackEnv } from "../typings/dockerStackEnv"; +import path from "path"; + +const PROJECT_ROOT = path.resolve(__dirname, "../.."); + +export async function validate(name: string): Promise { + const config: stackConfig = JSON.parse(await getStackConfig()); + if (!config.stacks.find((element) => element === name)) { + throw new Error("Stack not found"); + } + + return true; +} + +async function composeAction(option: string, name: string): Promise { + const composeFile: string = path.join(PROJECT_ROOT, `stacks/${name}`); + switch (option) { + case "start": { + await compose.upAll({ cwd: composeFile, log: false }).then( + () => { + return true; + }, + (err: unknown) => { + throw new Error(err as string); + }, + ); + break; + } + case "stop": { + await compose.downAll({ cwd: composeFile, log: false }).then( + () => { + return true; + }, + (err: unknown) => { + throw new Error(err as string); + }, + ); + break; + } + } +} + +class StackHandler { + private req: Request; + private res: Response; + + constructor(req: Request, res: Response) { + this.req = req; + this.res = res; + } + + async createStack(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const name: string = req.params.name; + const content: DockerComposeFile = req.body; + let override = false; + override = req.query.override == "true"; + + await createStack(name, content, override); + return ResponseHandler.ok("Stack created"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async start(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const name: string = req.params.name; + await validate(name); + await composeAction("start", name); + return ResponseHandler.ok("Stack started"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async stop(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const name: string = req.params.name; + await validate(name); + await composeAction("stop", name); + return ResponseHandler.ok("Stack stopped"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } + } + + async stackCompose(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const {name} = req.params; + return ResponseHandler.rawData( + await getStackCompose(name), + "Stack compose fetched", + ); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg.replace(/\n|\r/g, "")); + throw new Error(errorMsg); + } + } + + async setStackEnv(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const data: dockerStackEnv = req.body; + const name: string = req.params.name; + if (await writeEnvFile(name, data)) { + return ResponseHandler.ok("Wrote docker.env"); + } else { + return ResponseHandler.critical( + "Something went wrong while writing the env File!", + ); + } + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg.replace(/\n|\r/g, "")); + throw new Error(errorMsg); + } + } + + async getStackEnv(req: Request, res: Response) { + const ResponseHandler = createResponseHandler(res); + try { + const name: string = req.params.name; + const data = await getEnvFile(name); + if (data == null) { + return ResponseHandler.error( + "No environment file found for this Stack!", + 404, + ); + } + return ResponseHandler.rawData(data, "Read docker.env"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg.replace(/\n|\r/g, "")); + throw new Error(errorMsg); + } + } +} + +export const createStackHandler = (req: Request, res: Response) => + new StackHandler(req, res); diff --git a/src/init.ts b/src/init.ts index 8c757379..188542f6 100644 --- a/src/init.ts +++ b/src/init.ts @@ -7,27 +7,47 @@ import frontend from "./routes/frontendController/routes"; import api from "./routes/getter/routes"; import notificationService from "./routes/notifications/routes"; import conf from "./routes/setter/routes"; +import graph from "./routes/graphs/routes"; import authMiddleware from "./middleware/authMiddleware"; import ha from "./routes/highavailability/routes"; import trustedProxies from "./controllers/proxy"; import { limiter } from "./middleware/rateLimiter"; import { scheduleFetch } from "./controllers/scheduler"; +import { Server } from 'http'; import cors from "cors"; +import { setupWebSocket } from "./utils/webSocket"; +import stacks from "./routes/stack/routes"; import { blockWhileLocked } from "./middleware/checkLock"; import logger from "./utils/logger"; import initFiles from "./config/initFiles"; const LAB = [limiter, authMiddleware, blockWhileLocked]; -const initializeApp = (app: express.Application): void => { +const initializeApp = (app: express.Application, server: Server): void => { initFiles(); + + try { + logger.debug("Starting Websocket server, with these endpoints:"); + logger.debug("ws://localhost:9876/wss/container-data") + logger.debug("ws://localhost:9876/wss/server-logs") + setupWebSocket(server); + } catch (error: unknown) { + logger.error("Error starting WebSocket: ", error) + } + app.use(cors()); app.use(express.json()); - app.use("/api-docs", (req: Request, res: Response, next: NextFunction) => - next(), - ); - swaggerDocs(app); + if (process.env.NODE_ENV !== "production") { + app.use("/api-docs", (req: Request, res: Response, next: NextFunction) => + next(), + ); + app.get("/", (req: Request, res: Response) => { + res.redirect("/api-docs"); + }); + swaggerDocs(app); + } + trustedProxies(app); scheduleFetch(); @@ -36,13 +56,11 @@ const initializeApp = (app: express.Application): void => { app.use("/auth", LAB, auth); app.use("/data", LAB, data); app.use("/frontend", LAB, frontend); + app.use("/graph", LAB, graph); app.use("/notification-service", LAB, notificationService); + app.use("/stacks", LAB, stacks); app.use("/ha", limiter, authMiddleware, ha); - app.get("/", (req: Request, res: Response) => { - res.redirect("/api-docs"); - }); - process.on("exit", (code: number) => { logger.warn(`Server exiting (Code: ${code})`); }); diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 4afb3939..414b2762 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -36,7 +36,7 @@ async function authMiddleware( storedData.hash, ); if (!passwordMatch) { - ResponseHandler.denied("Invalid Password"); + ResponseHandler.error("Invalid Password", 402); return; } diff --git a/src/misc/createEnvDev.sh b/src/misc/createEnvDev.sh index 4a5a0bbe..1f231aa6 100755 --- a/src/misc/createEnvDev.sh +++ b/src/misc/createEnvDev.sh @@ -3,6 +3,9 @@ # Version VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" +# Automatic Stack environment management +AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT="${AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT:-true}" + # Docker if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then RUNNING_IN_DOCKER="true" @@ -10,11 +13,14 @@ else RUNNING_IN_DOCKER="false" fi +# Default dev log level +LOG_LEVEL="${LOG_LEVEL:-debug}" + echo -n "\ { \"VERSION\": \"${VERSION}\", \"RUNNING_IN_DOCKER\": \"${RUNNING_IN_DOCKER}\", - \"TRUSTED_PROXYS\": \"${TRUSTED_PROXYS}\", + \"TRUSTED_PROXIES\": \"${TRUSTED_PROXIES}\", \"HA_MASTER\": \"${HA_MASTER}\", \"HA_MASTER_IP\": \"${HA_MASTER_IP}\", \"HA_NODE\": \"${HA_NODE}\", @@ -31,6 +37,8 @@ echo -n "\ \"TELEGRAM_BOT_TOKEN\": \"${TELEGRAM_BOT_TOKEN}\", \"TELEGRAM_CHAT_ID\": \"${TELEGRAM_CHAT_ID}\", \"WHATSAPP_API_URL\": \"${WHATSAPP_API_URL}\", - \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\" + \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\", + \"AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT\": \"${AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT}\", + \"LOG_LEVEL\": \"${LOG_LEVEL}\" } \ -" > ./src/data/variables.json +" > ./src/data/variables.json || exit 1 diff --git a/src/misc/createEnvFile.sh b/src/misc/createEnvFile.sh index 754eab5a..0fbd15de 100755 --- a/src/misc/createEnvFile.sh +++ b/src/misc/createEnvFile.sh @@ -3,6 +3,9 @@ # Version VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" +# Automatic Stack environment management +AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT="${AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT:-true}" + # Docker if grep -q '/docker' /proc/1/cgroup 2>/dev/null || [ -f /.dockerenv ]; then RUNNING_IN_DOCKER="true" @@ -10,11 +13,14 @@ else RUNNING_IN_DOCKER="false" fi +# Default log level +LOG_LEVEL="${LOG_LEVEL:-info}" + echo -n "\ { \"VERSION\": \"${VERSION}\", \"RUNNING_IN_DOCKER\": \"${RUNNING_IN_DOCKER}\", - \"TRUSTED_PROXYS\": \"${TRUSTED_PROXYS}\", + \"TRUSTED_PROXIES\": \"${TRUSTED_PROXIES}\", \"HA_MASTER\": \"${HA_MASTER}\", \"HA_MASTER_IP\": \"${HA_MASTER_IP}\", \"HA_NODE\": \"${HA_NODE}\", @@ -31,6 +37,8 @@ echo -n "\ \"TELEGRAM_BOT_TOKEN\": \"${TELEGRAM_BOT_TOKEN}\", \"TELEGRAM_CHAT_ID\": \"${TELEGRAM_CHAT_ID}\", \"WHATSAPP_API_URL\": \"${WHATSAPP_API_URL}\", - \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\" + \"WHATSAPP_RECIPIENT\": \"${WHATSAPP_RECIPIENT}\", + \"AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT\": \"${AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT}\", + \"LOG_LEVEL\": \"${LOG_LEVEL}\" } \ -" > /api/src/data/variables.json +" > /api/src/data/variables.json || exit 1 diff --git a/src/misc/dependencyGraphs/createDependencyGraph.sh b/src/misc/dependencyGraphs/createDependencyGraph.sh index 4e118194..5fe007aa 100755 --- a/src/misc/dependencyGraphs/createDependencyGraph.sh +++ b/src/misc/dependencyGraphs/createDependencyGraph.sh @@ -11,7 +11,7 @@ spawn_worker(){ echo -e "\nRoute: $route \n${target_route}" - npx depcruise \ + test=true depcruise \ -c ./src/misc/dependencyGraphs/.dependency-cruiser.cjs \ -p cli-feedback \ -T mermaid \ diff --git a/src/misc/dependencyGraphs/mermaid-all.txt b/src/misc/dependencyGraphs/mermaid-all.txt index ad02a822..1cb2ebe8 100644 --- a/src/misc/dependencyGraphs/mermaid-all.txt +++ b/src/misc/dependencyGraphs/mermaid-all.txt @@ -2,106 +2,112 @@ flowchart TB subgraph 0["src"] 1["server.ts"] -subgraph 2["controllers"] -3["highAvailability.ts"] -C["proxy.ts"] -D["scheduler.ts"] -F["fetchData.ts"] -Q["auth.ts"] -X["frontendConfiguration.ts"] -end -subgraph 4["config"] -5["variables.ts"] -B["initFiles.ts"] -E["db.ts"] -end -subgraph 6["data"] -7["variables.json"] -end -subgraph 8["typings"] -9["ha.ts"] -end -A["init.ts"] -subgraph G["middleware"] -H["authMiddleware.ts"] -K["checkLock.ts"] -L["rateLimiter.ts"] -end -subgraph I["handlers"] -J["response.ts"] -P["auth.ts"] -T["data.ts"] -W["frontend.ts"] -10["api.ts"] +2["init.ts"] +subgraph 3["config"] +4["initFiles.ts"] +7["variables.ts"] +B["db.ts"] +end +subgraph 5["controllers"] +6["proxy.ts"] +A["scheduler.ts"] +C["fetchData.ts"] +N["auth.ts"] +U["frontendConfiguration.ts"] +14["highAvailability.ts"] +end +subgraph 8["data"] +9["variables.json"] +end +subgraph D["middleware"] +E["authMiddleware.ts"] +H["checkLock.ts"] +I["rateLimiter.ts"] +end +subgraph F["handlers"] +G["response.ts"] +M["auth.ts"] +Q["data.ts"] +T["frontend.ts"] +X["api.ts"] +10["graph.ts"] 13["ha.ts"] -16["notification.ts"] -19["conf.ts"] +19["notification.ts"] +1C["conf.ts"] end -subgraph M["routes"] -subgraph N["auth"] -O["routes.ts"] +subgraph J["routes"] +subgraph K["auth"] +L["routes.ts"] end -subgraph R["data"] +subgraph O["data"] +P["routes.ts"] +end +subgraph R["frontendController"] S["routes.ts"] end -subgraph U["frontendController"] -V["routes.ts"] +subgraph V["getter"] +W["routes.ts"] end -subgraph Y["getter"] +subgraph Y["graphs"] Z["routes.ts"] end subgraph 11["highavailability"] 12["routes.ts"] end -subgraph 14["notifications"] -15["routes.ts"] -end -subgraph 17["setter"] +subgraph 17["notifications"] 18["routes.ts"] end +subgraph 1A["setter"] +1B["routes.ts"] +end +end +subgraph 15["typings"] +16["ha.ts"] end end -1-->3 -1-->A -3-->5 -3-->9 -5-->7 +1-->2 +2-->4 +2-->6 +2-->A +2-->E +2-->H +2-->I +2-->L +2-->P +2-->S +2-->W +2-->Z +2-->12 +2-->18 +2-->1B +6-->7 +7-->9 A-->B A-->C -A-->D -A-->H -A-->K -A-->L -A-->O -A-->S -A-->V -A-->Z -A-->12 -A-->15 -A-->18 -C-->5 -D-->E -D-->F -F-->E -H-->J -K-->J -O-->P +C-->B +E-->G +H-->G +L-->M +M-->N +M-->G P-->Q -P-->J +Q-->B +Q-->G S-->T -T-->E -T-->J -V-->W +T-->U +T-->G W-->X -W-->J +X-->A +X-->G Z-->10 -10-->D -10-->J +Z-->G 12-->13 -13-->3 -13-->J -15-->16 -16-->J +13-->14 +13-->G +14-->7 +14-->16 18-->19 -19-->D -19-->J +19-->G +1B-->1C +1C-->A +1C-->G diff --git a/src/misc/dependencyGraphs/mermaid-graph.txt b/src/misc/dependencyGraphs/mermaid-graph.txt new file mode 100644 index 00000000..34484535 --- /dev/null +++ b/src/misc/dependencyGraphs/mermaid-graph.txt @@ -0,0 +1,15 @@ +flowchart TB + +subgraph 0["src"] +subgraph 1["routes"] +subgraph 2["graphs"] +3["routes.ts"] +end +end +subgraph 4["handlers"] +5["graph.ts"] +6["response.ts"] +end +end +3-->5 +3-->6 diff --git a/src/misc/entrypoint.sh b/src/misc/entrypoint.sh index 60b8a0e4..77b6236e 100755 --- a/src/misc/entrypoint.sh +++ b/src/misc/entrypoint.sh @@ -3,6 +3,12 @@ VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" +if [[ "$1" = "--dev" ]]; then + node_env="development" +elif [[ "$1" = "--prod" ]]; then + node_env="production" +fi + echo -e " \033[1;32mWelcome to\033[0m @@ -27,4 +33,4 @@ DockStat and DockStatAPI are 2 fully OpenSource projects, DockStatAPI is a simpl bash ./createEnvFile.sh -exec node src/server.js +NODE_ENV=${node_env} node src/server.js diff --git a/src/misc/minifyDist.sh b/src/misc/minifyDist.sh index 8a85b162..171ef095 100755 --- a/src/misc/minifyDist.sh +++ b/src/misc/minifyDist.sh @@ -4,7 +4,7 @@ dist="$(pwd)/dist" run_script() { npx uglifyjs --no-annotations --in-situ "$1" > /dev/null - echo "✔️ Minified : $(basename "$1")" + echo "✔️ Minified : $(basename "$1")" } if [ -d "$dist" ]; then diff --git a/src/routes/auth/routes.ts b/src/routes/auth/routes.ts index 47ff6f25..03549bfa 100644 --- a/src/routes/auth/routes.ts +++ b/src/routes/auth/routes.ts @@ -3,54 +3,12 @@ import { createAuthenticationHandler } from "../../handlers/auth"; const router = Router(); -/** - * @swagger - * /auth/enable: - * post: - * summary: Enable authentication by setting a password - * tags: [Authentication] - * parameters: - * - name: password - * in: query - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Authentication enabled. - * 400: - * description: Password is required. - * 500: - * description: Error saving password. - */ router.post("/enable", async (req: Request, res: Response): Promise => { const password = req.query.password as string; const handler = createAuthenticationHandler(req, res); await handler.enable(password); }); -/** - * @swagger - * /auth/disable: - * post: - * summary: Disable authentication by providing the existing password - * tags: [Authentication] - * parameters: - * - name: password - * in: query - * required: true - * schema: - * type: string - * responses: - * 200: - * description: Authentication disabled. - * 400: - * description: Password is required. - * 401: - * description: Invalid password. - * 500: - * description: Error disabling authentication. - */ router.post("/disable", async (req: Request, res: Response): Promise => { const password = req.query.password as string; const handler = createAuthenticationHandler(req, res); diff --git a/src/routes/data/routes.ts b/src/routes/data/routes.ts index 92a7f976..93c4610b 100644 --- a/src/routes/data/routes.ts +++ b/src/routes/data/routes.ts @@ -2,150 +2,16 @@ import express, { Request, Response } from "express"; const router = express.Router(); import { createDatabaseHandler } from "../../handlers/data"; -/** - * @swagger - * /data/latest: - * get: - * summary: Retrieve the latest container statistics for a specific host - * tags: [Database queries] - * responses: - * 200: - * description: A JSON object containing the latest container statistics for the specified host. - * content: - * application/json: - * schema: - * type: object - * properties: - * Fin-2: - * type: array - * items: - * type: object - * properties: - * name: - * type: string - * description: The name of the container - * example: "Container A" - * id: - * type: string - * description: Unique identifier for the container - * example: "abcd1234" - * hostName: - * type: string - * description: Name of the host system running this container - * example: "Fin-2" - * state: - * type: string - * description: Current state of the container - * example: "running" - * cpu_usage: - * type: number - * description: CPU usage percentage for this container - * example: 30 - * mem_usage: - * type: number - * description: Memory usage in bytes - * example: 2097152 - * mem_limit: - * type: number - * description: Memory limit in bytes set for this container - * example: 8123764736 - * net_rx: - * type: number - * description: Total network received bytes since container start - * example: 151763111 - * net_tx: - * type: number - * description: Total network transmitted bytes since container start - * example: 7104386 - * current_net_rx: - * type: number - * description: Current received bytes in the recent period - * example: 1048576 - * current_net_tx: - * type: number - * description: Current transmitted bytes in the recent period - * example: 524288 - * networkMode: - * type: string - * description: Networking mode for the container - * example: "bridge" - */ router.get("/latest", (req: Request, res: Response) => { const DatabaseHandler = createDatabaseHandler(req, res); return DatabaseHandler.latest(); }); -/** - * @swagger - * /data/all: - * get: - * summary: Retrieve container statistics entries from the last 24 hours - * tags: [Database queries] - * responses: - * 200: - * description: A numbered array of 'info' JSON objects from the last 24 hours. - * content: - * application/json: - * schema: - * type: object - * properties: - * 0: - * type: object - * description: Statistics for the first entry within 24 hours. - * properties: - * name: - * type: string - * example: "Container A" - * id: - * type: string - * example: "abcd1234" - * cpu_usage: - * type: number - * example: 30 - * mem_usage: - * type: number - * example: 2048 - * 1: - * type: object - * description: Statistics for the second entry within 24 hours. - * properties: - * name: - * type: string - * example: "Container B" - * id: - * type: string - * example: "efgh5678" - * cpu_usage: - * type: number - * example: 45 - * mem_usage: - * type: number - * example: 3072 - */ router.get("/all", (req: Request, res: Response) => { const DatabaseHandler = createDatabaseHandler(req, res); return DatabaseHandler.all(); }); -/** - * @swagger - * /data/clear: - * delete: - * summary: Clear all container statistics entries from the database - * tags: [Database queries] - * responses: - * 200: - * description: A message indicating whether the database was cleared successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * description: Success message upon database clearance - * example: "Database cleared successfully." - */ router.delete("/clear", (req: Request, res: Response) => { const DatabaseHandler = createDatabaseHandler(req, res); return DatabaseHandler.clear(); diff --git a/src/routes/frontendController/routes.ts b/src/routes/frontendController/routes.ts index 39500c51..723afa47 100644 --- a/src/routes/frontendController/routes.ts +++ b/src/routes/frontendController/routes.ts @@ -2,259 +2,30 @@ import express from "express"; const router = express.Router(); import { createFrontendHandler } from "../../handlers/frontend"; -/** - * @swagger - * /frontend/show/{containerName}: - * post: - * summary: Unhide a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to unhide - * responses: - * 200: - * description: Container unhidden successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.post("/show/:containerName", async (req, res) => { const FrontendHandler = createFrontendHandler(req, res); const containerName = req.params.containerName; return FrontendHandler.show(containerName); }); -/** - * @swagger - * /frontend/tag/{containerName}/{tag}: - * post: - * summary: Add a tag to a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to add tag to - * - in: path - * name: tag - * schema: - * type: string - * required: true - * description: The tag to add - * responses: - * 200: - * description: Tag added successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.post("/tag/:containerName/:tag", async (req, res) => { const { containerName, tag } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.addTag(containerName, tag); }); -/** - * @swagger - * /frontend/pin/{containerName}: - * post: - * summary: Pin a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to pin - * responses: - * 200: - * description: Container pinned successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.post("/pin/:containerName", async (req, res) => { const { containerName } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.pin(containerName); }); -/** - * @swagger - * /frontend/add-link/{containerName}/{link}: - * post: - * summary: Add a link to a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to add link to - * - in: path - * name: link - * schema: - * type: string - * required: true - * description: The link to add - * responses: - * 200: - * description: Link added successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.post("/add-link/:containerName/:link", async (req, res) => { const { containerName, link } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.addLink(containerName, link); }); -/** - * @swagger - * /frontend/add-icon/{containerName}/{icon}/{useCustomIcon}: - * post: - * summary: Add an Icon to a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to add link to - * - in: path - * name: icon - * schema: - * type: string - * required: true - * description: The Icon to add - * - in: path - * name: useCustomIcon - * shema: - * type: boolean - * required: false - * description: If this icon is a custom icon or nor - * responses: - * 200: - * description: Icon added successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.post( "/add-icon/:containerName/:icon/:useCustomIcon", async (req, res) => { @@ -272,242 +43,30 @@ router.post( |____/|_____|_____|_____| |_| |_____| */ -/** - * @swagger - * /frontend/hide/{containerName}: - * delete: - * summary: Hide a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to hide - * responses: - * 200: - * description: Container hidden successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ -// Hide a container router.delete("/hide/:containerName", async (req, res) => { const { containerName } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.hide(containerName); }); -/** - * @swagger - * /frontend/remove-tag/{containerName}/{tag}: - * delete: - * summary: Remove a tag from a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to remove tag from - * - in: path - * name: tag - * schema: - * type: string - * required: true - * description: The tag to remove - * responses: - * 200: - * description: Tag removed successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.delete("/remove-tag/:containerName/:tag", async (req, res) => { const { containerName, tag } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.removeTag(containerName, tag); }); -/** - * @swagger - * /frontend/unpin/{containerName}: - * delete: - * summary: Unpin a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to unpin - * responses: - * 200: - * description: Container unpinned successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.delete("/unpin/:containerName", async (req, res) => { const { containerName } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.unPin(containerName); }); -/** - * @swagger - * /frontend/remove-link/{containerName}: - * delete: - * summary: Remove a link from a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to remove link from - * responses: - * 200: - * description: Link removed successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.delete("/remove-link/:containerName", async (req, res) => { const { containerName } = req.params; const FrontendHandler = createFrontendHandler(req, res); return FrontendHandler.removeLink(containerName); }); -/** - * @swagger - * /frontend/remove-icon/{containerName}: - * delete: - * summary: Remove an icon from a container - * tags: [Frontend Configuration] - * parameters: - * - in: path - * name: containerName - * schema: - * type: string - * required: true - * description: The name of the container to remove the icon from - * responses: - * 200: - * description: Icon removed successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * error: - * type: string - * description: Error message - */ router.delete("/remove-icon/:containerName", async (req, res) => { const { containerName } = req.params; const FrontendHandler = createFrontendHandler(req, res); diff --git a/src/routes/getter/routes.ts b/src/routes/getter/routes.ts index 0912d48b..d08ae511 100644 --- a/src/routes/getter/routes.ts +++ b/src/routes/getter/routes.ts @@ -2,315 +2,42 @@ import { Router, Request, Response } from "express"; import { createApiHandler } from "../../handlers/api"; const router = Router(); -/** - * @swagger - * /api/hosts: - * get: - * summary: Retrieve a list of all available Docker hosts - * tags: [Hosts] - * responses: - * 200: - * description: A JSON object containing an array of host names. - * content: - * application/json: - * schema: - * type: object - * properties: - * hosts: - * type: array - * items: - * type: string - * example: ["local", "remote1"] - */ router.get("/hosts", (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.hosts(); }); -/** - * @swagger - * /api/system: - * get: - * summary: Retrieve system configuration details - * tags: [Misc] - * responses: - * 200: - * description: A JSON object containing the system configuration details. - * content: - * application/json: - * schema: - * type: object - * description: The parsed configuration details. - * 500: - * description: An error occurred while fetching the system configuration. - * content: - * application/json: - * schema: - * type: object - * properties: - * error: - * type: string - * description: Error message detailing the issue encountered. - */ router.get("/system", (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.system(); }); -/** - * @swagger - * /api/host/{hostName}/stats: - * get: - * summary: Retrieve statistics for a specified Docker host - * tags: [Hosts] - * parameters: - * - name: hostName - * in: path - * required: true - * description: The name of the host for which to fetch statistics. - * schema: - * type: string - * responses: - * 200: - * description: A JSON object containing relevant statistics for the specified host. - * content: - * application/json: - * schema: - * type: object - * properties: - * hostName: - * type: string - * description: The name of the Docker host. - * info: - * type: object - * description: Information about the Docker host (e.g., storage, running containers). - * version: - * type: object - * description: Version details of the Docker installation on the host. - * 500: - * description: An error occurred while fetching host statistics. - * content: - * application/json: - * schema: - * type: object - * properties: - * error: - * type: string - * description: Error message detailing the issue encountered. - */ router.get("/host/:hostName/stats", async (req: Request, res: Response) => { const { hostName } = req.params; const ApiHandler = createApiHandler(req, res); return ApiHandler.hostStats(hostName); }); -/** - * @swagger - * /api/containers: - * get: - * summary: Retrieve all Docker containers across all configured hosts - * tags: [Containers] - * responses: - * 200: - * description: A JSON object containing container data for all hosts. - * content: - * application/json: - * schema: - * type: object - * additionalProperties: - * type: object - * properties: - * name: - * type: string - * description: Name of the container. - * id: - * type: string - * description: Unique identifier for the container. - * hostName: - * type: string - * description: The host on which the container is running. - * state: - * type: string - * description: Current state of the container (e.g., running, exited). - * cpu_usage: - * type: number - * format: double - * description: CPU usage in nanoseconds. - * mem_usage: - * type: number - * description: Memory usage in bytes. - * mem_limit: - * type: number - * description: Memory limit in bytes. - * net_rx: - * type: number - * description: Total received bytes over the network. - * net_tx: - * type: number - * description: Total transmitted bytes over the network. - * current_net_rx: - * type: number - * description: Current received bytes over the network. - * current_net_tx: - * type: number - * description: Current transmitted bytes over the network. - * networkMode: - * type: string - * description: Network mode configured for the container. - * link: - * type: string - * description: Optional link to additional information. - * icon: - * type: string - * description: Optional icon representing the container. - * tags: - * type: string - * description: Optional tags associated with the container. - * 500: - * description: An error occurred while fetching container data. - * content: - * application/json: - * schema: - * type: object - * properties: - * error: - * type: string - * description: Error message detailing the issue encountered. - */ router.get("/containers", async (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.containers(); }); -/** - * @swagger - * /api/config: - * get: - * summary: Retrieve Docker configuration - * tags: [Configuration] - * responses: - * 200: - * description: A JSON object containing the Docker configuration. - * content: - * application/json: - * schema: - * type: object - * additionalProperties: true - * 500: - * description: An error occurred while loading the Docker configuration. - * content: - * application/json: - * schema: - * type: object - * properties: - * error: - * type: string - * description: Error message detailing the issue encountered. - */ router.get("/config", async (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.config(); }); -/** - * @swagger - * /api/current-schedule: - * get: - * summary: Get the current fetch schedule in seconds - * tags: [Configuration] - * responses: - * 200: - * description: Current fetch schedule retrieved successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * interval: - * type: integer - * description: Current fetch interval in seconds. - */ router.get("/current-schedule", (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.currentSchedule(); }); -/** - * @swagger - * /api/status: - * get: - * summary: Check the DockStatAPI and docker socket status of each host - * tags: [Misc] - * description: Returns the status of the backend and online components, indicating which nodes are reachable or offline. - * responses: - * 200: - * description: Server and backend status - * content: - * application/json: - * schema: - * type: object - * properties: - * backendReachable: - * type: boolean - * example: true - * online: - * type: object - * properties: - * Host-1: - * type: boolean - * example: true - * Host-2: - * type: boolean - * example: false - */ - router.get("/status", async (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.status(); }); -/** - * @swagger - * /api/frontend-config: - * get: - * summary: Get Frontend Configuration - * tags: [Configuration] - * description: Retrieves the frontend configuration data. - * responses: - * 200: - * description: Success - * content: - * application/json: - * schema: - * type: array - * items: - * type: object - * properties: - * name: - * type: string - * description: Container Name - * hidden: - * type: boolean - * description: Whether the container is hidden - * tags: - * type: array - * items: - * type: string - * description: Tags associated with the container - * pinned: - * type: boolean - * description: Whether the container is pinned - * 500: - * description: Internal Server Error - * content: - * application/json: - * schema: - * type: object - * properties: - * error: - * type: string - * description: Error message - */ router.get("/frontend-config", (req: Request, res: Response) => { const ApiHandler = createApiHandler(req, res); return ApiHandler.frontendConfig(); diff --git a/src/routes/graphs/routes.ts b/src/routes/graphs/routes.ts new file mode 100644 index 00000000..db532058 --- /dev/null +++ b/src/routes/graphs/routes.ts @@ -0,0 +1,31 @@ +import { Request, Response, Router } from "express"; +import { createResponseHandler } from "../../handlers/response"; +import path from "path"; +const router = Router(); + +router.get("/", async (req: Request, res: Response) => { + const ResponseHandler = createResponseHandler(res); + try { + const graphPath = path.join( + __dirname, + "/../../.." + "/src/data/graph.html", + ); + return res.contentType("html").status(200).sendFile(graphPath); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } +}); + +router.get("/image", async (req: Request, res: Response) => { + const ResponseHandler = createResponseHandler(res); + try { + const graphPath = path.join(__dirname, "/../../.." + "/src/data/graph.png"); + return res.contentType("image/png").status(200).sendFile(graphPath); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } +}); + +export default router; diff --git a/src/routes/highavailability/routes.ts b/src/routes/highavailability/routes.ts index 86057bcd..d4adc466 100644 --- a/src/routes/highavailability/routes.ts +++ b/src/routes/highavailability/routes.ts @@ -3,31 +3,11 @@ import { SyncRequestBody } from "../../typings/syncRequestBody"; import { createHaHandler } from "../../handlers/ha"; const router = Router(); -/** - * @swagger - * /ha/config: - * get: - * summary: Retrieve the High Availability Config - * tags: [High Availability] - * responses: - * 200: - * description: A JSON object containing the config. - */ router.get("/config", async (req: Request, res: Response) => { const HaHandler = createHaHandler(req, res); return HaHandler.config(); }); -/** - * @swagger - * /ha/sync: - * post: - * summary: Synchronize configuration files from master node. - * tags: [High Availability] - * responses: - * 200: - * description: Files synchronized successfully. - */ router.post( "/sync", async ( @@ -39,16 +19,6 @@ router.post( }, ); -/** - * @swagger - * /ha/prepare-sync: - * get: - * summary: Prepare files for synchronization. - * tags: [High Availability] - * responses: - * 200: - * description: A JSON object containing files to sync. - */ router.get("/prepare-sync", async (req: Request, res: Response) => { const HaHandler = createHaHandler(req, res); return HaHandler.prepare(); diff --git a/src/routes/notifications/routes.ts b/src/routes/notifications/routes.ts index 4544b8ce..13b754bd 100644 --- a/src/routes/notifications/routes.ts +++ b/src/routes/notifications/routes.ts @@ -2,125 +2,16 @@ import { Request, Response, Router } from "express"; import { createNotificationHandler } from "../../handlers/notification"; const router = Router(); -/** - * @swagger - * /notification-service/get-template: - * get: - * summary: Retrieve the notification template - * tags: [Notification Service] - * responses: - * 200: - * description: Template data retrieved successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * description: Indicates if the operation was successful - * data: - * type: object - * description: The template data in JSON format - * 500: - * description: Internal server error. - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * description: Error message - */ router.get("/get-template", (req: Request, res: Response) => { const NotificationHandler = createNotificationHandler(req, res); return NotificationHandler.getTemplate(); }); -/** - * @swagger - * /notification-service/set-template: - * post: - * summary: Update the notification template - * tags: [Notification Service] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * description: New template data to save - * responses: - * 200: - * description: Template updated successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * description: Success message - * 500: - * description: Internal server error. - * content: - * application/json: - * schema: - * type: object - * properties: - * message: - * type: string - * description: Error message - */ router.post("/set-template", (req: Request, res: Response): void => { const NotificationHandler = createNotificationHandler(req, res); return NotificationHandler.setTemplate(req); }); -/** - * @swagger - * /notification-service/test/{type}/{containerId}: - * post: - * summary: Send a test notification for a specific container - * tags: [Notification Service] - * parameters: - * - in: path - * name: type - * schema: - * type: string - * required: true - * description: Type of notification to test - * - in: path - * name: containerId - * schema: - * type: string - * required: true - * description: The ID of the container for the notification test - * responses: - * 200: - * description: Test notification sent successfully. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - * 500: - * description: Internal server error. - * content: - * application/json: - * schema: - * type: object - * properties: - * success: - * type: boolean - * message: - * type: string - */ router.post("/test/:type/:containerId", async (req: Request, res: Response) => { const NotificationHandler = createNotificationHandler(req, res); NotificationHandler.test(req); diff --git a/src/routes/setter/routes.ts b/src/routes/setter/routes.ts index 75ef747d..16150293 100644 --- a/src/routes/setter/routes.ts +++ b/src/routes/setter/routes.ts @@ -1,83 +1,20 @@ import express, { Router, Request, Response } from "express"; -import { createConfHandler } from "../../handlers/conf"; const router: Router = express.Router(); +import { createConfHandler } from "../../handlers/conf"; -/** - * @swagger - * /conf/addHost: - * put: - * summary: Add a new host to the Docker configuration - * tags: [Configuration] - * parameters: - * - name: name - * in: query - * required: true - * description: The name of the new host. - * - name: url - * in: query - * required: true - * description: The URL of the new host. - * - name: port - * in: query - * required: true - * description: The port of the new host. - * responses: - * 200: - * description: Host added successfully. - * 400: - * description: Bad request, invalid input. - * 500: - * description: An error occurred while adding the host. - */ router.put("/addHost", async (req: Request, res: Response): Promise => { const ConfHandler = createConfHandler(req, res); return ConfHandler.addHost(req); }); -/** - * @swagger - * /conf/scheduler: - * put: - * summary: Set fetch interval for data fetching - * tags: [Configuration] - * parameters: - * - name: interval - * in: query - * required: true - * description: The new interval for fetching data, e.g., "6h 20m", "300s". - * responses: - * 200: - * description: Fetch interval set successfully. - * 400: - * description: Invalid interval format or out of range. - */ -router.put("/scheduler", (req: Request, res: Response) => { +router.delete("/removeHost", (req: Request, res: Response): void => { const ConfHandler = createConfHandler(req, res); - return ConfHandler.scheduler(req); + return ConfHandler.removeHost(req); }); -/** - * @swagger - * /conf/removeHost: - * delete: - * summary: Remove a host from the Docker configuration - * tags: [Configuration] - * parameters: - * - name: hostName - * in: query - * required: true - * description: The name of the host to remove. - * responses: - * 200: - * description: Host removed successfully. - * 404: - * description: Host not found. - * 500: - * description: An error occurred while removing the host. - */ -router.delete("/removeHost", (req: Request, res: Response): void => { +router.put("/scheduler", (req: Request, res: Response) => { const ConfHandler = createConfHandler(req, res); - return ConfHandler.addHost(req); + return ConfHandler.scheduler(req); }); export default router; diff --git a/src/routes/stack/routes.ts b/src/routes/stack/routes.ts new file mode 100644 index 00000000..8f9b9ae8 --- /dev/null +++ b/src/routes/stack/routes.ts @@ -0,0 +1,35 @@ +import express, { Router, Request, Response } from "express"; +const router: Router = express.Router(); +import { createStackHandler } from "../../handlers/stack"; + +router.post("/create/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return StackHandler.createStack(req, res); +}); + +router.post("/start/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return StackHandler.start(req, res); +}); + +router.post("/stop/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return StackHandler.stop(req, res); +}); + +router.get("/get/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return await StackHandler.stackCompose(req, res); +}); + +router.post("/set-env/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return await StackHandler.setStackEnv(req, res); +}); + +router.get("/get-env/:name", async (req: Request, res: Response) => { + const StackHandler = createStackHandler(req, res); + return await StackHandler.getStackEnv(req, res); +}); + +export default router; diff --git a/src/sample-variable.json b/src/sample-variable.json index 06153af5..f507796b 100644 --- a/src/sample-variable.json +++ b/src/sample-variable.json @@ -1,7 +1,7 @@ { "VERSION": "", "RUNNING_IN_DOCKER": "", - "TRUSTED_PROXYS": "", + "TRUSTED_PROXIES": "", "HA_MASTER": "", "HA_MASTER_IP": "", "HA_NODE": "", @@ -18,5 +18,7 @@ "TELEGRAM_BOT_TOKEN": "", "TELEGRAM_CHAT_ID": "", "WHATSAPP_API_URL": "", - "WHATSAPP_RECIPIENT": "" + "WHATSAPP_RECIPIENT": "", + "AUTOMATIC_ENVIRONMENT_FILE_MANAGEMENT": "true", + "LOG_LEVEL": "info" } diff --git a/src/server.ts b/src/server.ts index 97e5337a..edcb2ec5 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,14 +1,18 @@ import express from "express"; import initializeApp from "./init"; -import { startMasterNode } from "./controllers/highAvailability"; import writeUserConf from "./config/hostsystem"; +import { startServer } from "./utils/startServer"; +import http from "http"; +const port: number = parseInt(process.env.PORT || "9876"); const app = express(); -const PORT: number = 9876; +const server = http.createServer(app); -writeUserConf(); -initializeApp(app); +initializeApp(app, server); -app.listen(PORT, () => { - startMasterNode(); -}); +if (process.env.NODE_ENV !== "testing") { + writeUserConf(port); + startServer(app, server, port); +} + +export default app; \ No newline at end of file diff --git a/src/typings/dockerCompose.ts b/src/typings/dockerCompose.ts new file mode 100644 index 00000000..e30f7e0d --- /dev/null +++ b/src/typings/dockerCompose.ts @@ -0,0 +1,92 @@ +export interface DockerComposeFile { + services: Record; + networks?: Record; + volumes?: Record; +} + +export interface ServiceDefinition { + image?: string; + build?: BuildDefinition; + container_name?: string; + command?: string | string[]; + environment?: Record; + ports?: string[] | PortMapping[]; + volumes?: string[]; + networks?: string[]; + restart?: string; + depends_on?: string[]; + deploy?: DeployDefinition; + env_file?: string[]; +} + +export interface BuildDefinition { + context: string; + dockerfile?: string; + args?: Record; + cache_from?: string[]; + labels?: Record; + target?: string; +} + +export interface PortMapping { + target: number; + published: number; + protocol?: "tcp" | "udp"; + mode?: "host" | "ingress"; +} + +export interface DeployDefinition { + replicas?: number; + resources?: ResourcesDefinition; + restart_policy?: RestartPolicyDefinition; + labels?: Record; + update_config?: UpdateConfigDefinition; +} + +export interface ResourcesDefinition { + limits?: ResourceLimits; + reservations?: ResourceReservations; +} + +export interface ResourceLimits { + cpus?: string; + memory?: string; +} + +export interface ResourceReservations { + cpus?: string; + memory?: string; +} + +export interface RestartPolicyDefinition { + condition?: "none" | "on-failure" | "any"; + delay?: string; + max_attempts?: number; + window?: string; +} + +export interface UpdateConfigDefinition { + parallelism?: number; + delay?: string; + failure_action?: "continue" | "pause"; + monitor?: string; + max_failure_ratio?: number; + order?: "start-first" | "stop-first"; +} + +export interface NetworkDefinition { + driver?: string; + driver_opts?: Record; + attachable?: boolean; + external?: boolean; + internal?: boolean; + labels?: Record; +} + +export interface VolumeDefinition { + driver?: string; + driver_opts?: Record; + external?: boolean; + labels?: Record; + name?: string; +} diff --git a/src/typings/dockerStackEnv.ts b/src/typings/dockerStackEnv.ts new file mode 100644 index 00000000..c784b85d --- /dev/null +++ b/src/typings/dockerStackEnv.ts @@ -0,0 +1,10 @@ +interface dockerStackProperty { + name: string; + value: string; +} + +interface dockerStackEnv { + environment: dockerStackProperty[]; +} + +export { dockerStackEnv, dockerStackProperty }; diff --git a/src/typings/ha.ts b/src/typings/ha.ts index a722fff8..f0352fc0 100644 --- a/src/typings/ha.ts +++ b/src/typings/ha.ts @@ -6,7 +6,7 @@ interface HighAvailabilityConfig { interface Node { ip: string; - id: number; + port: number; } interface HaNodeConfig { diff --git a/src/typings/stackConfig.ts b/src/typings/stackConfig.ts new file mode 100644 index 00000000..45c72553 --- /dev/null +++ b/src/typings/stackConfig.ts @@ -0,0 +1,5 @@ +interface stackConfig { + stacks: string[]; +} + +export { stackConfig }; diff --git a/src/utils/assets/api-icon.svg b/src/utils/assets/api-icon.svg new file mode 100644 index 00000000..5a4fdb7c --- /dev/null +++ b/src/utils/assets/api-icon.svg @@ -0,0 +1 @@ +\ diff --git a/src/utils/assets/container-icon.svg b/src/utils/assets/container-icon.svg new file mode 100644 index 00000000..15ed98c6 --- /dev/null +++ b/src/utils/assets/container-icon.svg @@ -0,0 +1 @@ +\ diff --git a/src/utils/assets/server-icon.svg b/src/utils/assets/server-icon.svg new file mode 100644 index 00000000..31c92d4a --- /dev/null +++ b/src/utils/assets/server-icon.svg @@ -0,0 +1 @@ +\ diff --git a/src/utils/atomicWrite.ts b/src/utils/atomicWrite.ts index 51f33759..d279475e 100644 --- a/src/utils/atomicWrite.ts +++ b/src/utils/atomicWrite.ts @@ -4,7 +4,7 @@ import { AtomicWriteOptions } from "../typings/atomicWrite"; export function atomicWrite( targetPath: string, - data: string | Buffer | Record, + data: object | string | Buffer | Record, options: AtomicWriteOptions = {}, ): void { const { mode = 0o600, exclusive = false } = options; diff --git a/src/utils/connectionChecker.ts b/src/utils/connectionChecker.ts index 85b00dde..5a45505b 100644 --- a/src/utils/connectionChecker.ts +++ b/src/utils/connectionChecker.ts @@ -59,8 +59,8 @@ async function checkReachability(): Promise { const hosts: target[] = parsedData.hosts; return await checkHostStatus(hosts); } catch (error: unknown) { - logger.error(`Error reading file: ${error as Error}`); - return undefined; + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts index f9277c1a..86dc2d38 100644 --- a/src/utils/containerService.ts +++ b/src/utils/containerService.ts @@ -1,16 +1,18 @@ import logger from "./logger"; -import { ContainerInfo, ContainerStats, ContainerInspectInfo } from "dockerode"; -import getDockerClient from "./dockerClient"; +import { ContainerInfo, } from "dockerode"; +import { getDockerClient } from "./dockerClient"; import fs from "fs"; import { atomicWrite } from "./atomicWrite"; const configPath = "./src/data/dockerConfig.json"; import { AllContainerData, HostConfig } from "../typings/dockerConfig"; +import { generateGraphFiles } from "../handlers/graph"; +import { WebSocket } from "ws"; -function loadConfig() { +export function loadConfig() { try { if (!fs.existsSync(configPath)) { logger.warn( - `Config file not found. Creating an empty file at ${configPath}`, + `Config file not found. Creating an empty file at ${configPath}` ); atomicWrite(configPath, JSON.stringify({ hosts: [] }, null, 2)); } @@ -19,96 +21,147 @@ function loadConfig() { logger.debug("Loaded " + configPath); return JSON.parse(configData); } catch (error: unknown) { - logger.error(`Failed to load config: ${(error as Error).message}`); - return null; + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + return { hosts: [] }; } } -async function fetchAllContainers(): Promise { +export async function fetchContainersForHost(hostName: string) { const config = loadConfig(); - if (!config || !config.hosts) { - logger.error("Invalid or missing host configuration."); - return {}; + const hostConfig = config.hosts.find((h: HostConfig) => h.name === hostName); + + if (!hostConfig) { + throw new Error(`Host ${hostName} not found in configuration`); } - const allContainerData: AllContainerData = {}; + try { + const docker = getDockerClient(hostName); + const containers: ContainerInfo[] = await docker.listContainers({ all: true }); - for (const hostConfig of config.hosts as HostConfig[]) { - const hostName = hostConfig.name; - try { - const docker = getDockerClient(hostName); - logger.debug(`Now processing: ${hostName}`); - const containers: ContainerInfo[] = await docker.listContainers({ - all: true, - }); - - allContainerData[hostName] = await Promise.all( - containers.map(async (container) => { - try { - const containerInstance = docker.getContainer(container.Id); - const containerInfo: ContainerInspectInfo = - await containerInstance.inspect(); - const containerStats: ContainerStats = - await containerInstance.stats({ stream: false }); - - const cpuDelta = - containerStats.cpu_stats.cpu_usage.total_usage - - containerStats.precpu_stats.cpu_usage.total_usage; - const systemCpuDelta = - containerStats.cpu_stats.system_cpu_usage - - containerStats.precpu_stats.system_cpu_usage; - const cpuUsage = - systemCpuDelta > 0 - ? (cpuDelta / systemCpuDelta) * - containerStats.cpu_stats.online_cpus - : 0; - - return { - name: container.Names[0].replace("/", ""), - id: container.Id, - hostName, - state: container.State, - cpu_usage: cpuUsage * 1000000000, - mem_usage: containerStats.memory_stats.usage, - mem_limit: containerStats.memory_stats.limit, - net_rx: containerStats.networks?.eth0?.rx_bytes || 0, - net_tx: containerStats.networks?.eth0?.tx_bytes || 0, - current_net_rx: containerStats.networks?.eth0?.rx_bytes || 0, - current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, - networkMode: containerInfo.HostConfig.NetworkMode || "unknown", - }; - } catch (containerError: unknown) { - logger.error( - `Error fetching details for container ID: ${container.Id} on host: ${hostName} - ${(containerError as Error).message}`, - ); - return { - name: container.Names[0].replace("/", ""), - id: container.Id, - hostName, - state: container.State, - cpu_usage: 0, - mem_usage: 0, - mem_limit: 0, - net_rx: 0, - net_tx: 0, - current_net_rx: 0, - current_net_tx: 0, - networkMode: "unknown", - }; - } - }), - ); - } catch (error: unknown) { - logger.error( - `Error fetching containers for host: ${hostName} - ${(error as Error).message}. Stack: ${(error as Error).stack}`, - ); - allContainerData[hostName] = { - error: `Error fetching containers: ${(error as Error).message}`, - }; - } + return await Promise.all( + containers.map(async (container) => { + try { + const containerInstance = docker.getContainer(container.Id); + const [containerInfo, containerStats] = await Promise.all([ + containerInstance.inspect(), + containerInstance.stats({ stream: false }), + ]); + + const cpuDelta = + containerStats.cpu_stats.cpu_usage.total_usage - + containerStats.precpu_stats.cpu_usage.total_usage; + const systemCpuDelta = + containerStats.cpu_stats.system_cpu_usage - + containerStats.precpu_stats.system_cpu_usage; + const cpuUsage = + systemCpuDelta > 0 + ? (cpuDelta / systemCpuDelta) * containerStats.cpu_stats.online_cpus + : 0; + + return { + name: container.Names[0].replace("/", ""), + id: container.Id, + hostName, + state: container.State, + cpu_usage: cpuUsage, + mem_usage: containerStats.memory_stats.usage, + mem_limit: containerStats.memory_stats.limit, + net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + current_net_rx: containerStats.networks?.eth0?.rx_bytes || 0, + current_net_tx: containerStats.networks?.eth0?.tx_bytes || 0, + networkMode: containerInfo.HostConfig.NetworkMode || "unknown", + }; + } catch (error) { + logger.error(`Error processing container ${container.Id}: ${error}`); + return { + name: container.Names[0].replace("/", ""), + id: container.Id, + hostName, + state: container.State, + cpu_usage: 0, + mem_usage: 0, + mem_limit: 0, + net_rx: 0, + net_tx: 0, + current_net_rx: 0, + current_net_tx: 0, + networkMode: "unknown", + }; + } + }) + ); + } catch (error) { + logger.error(`Error fetching containers for ${hostName}: ${error}`); + throw error; } +} + +export async function fetchAllContainers(): Promise { + const config = loadConfig(); + const allContainerData: AllContainerData = {}; + await Promise.all( + config.hosts.map(async (hostConfig: HostConfig) => { + try { + allContainerData[hostConfig.name] = await fetchContainersForHost(hostConfig.name); + } catch (error) { + allContainerData[hostConfig.name] = { + error: `Error fetching containers: ${error instanceof Error ? error.message : String(error)}` + }; + } + }) + ); + + generateGraphFiles(allContainerData); return allContainerData; } -export default fetchAllContainers; +export async function streamContainerData(ws: WebSocket, hostName: string) { + try { + const containers = await fetchContainersForHost(hostName); + ws.send(JSON.stringify({ type: "containers", data: containers })); + + const docker = getDockerClient(hostName); + const eventStream = await docker.getEvents(); + + // eslint-disable-next-line + if (!(eventStream instanceof require('stream').Readable)) { + throw new Error('Failed to get valid event stream'); + } + + const handleData = (chunk: Buffer) => { + ws.send(JSON.stringify({ type: "container-event", data: chunk.toString() })); + }; + + const handleError = (err: Error) => { + logger.error(`Event stream error for ${hostName}: ${err.message}`); + ws.close(); + }; + + eventStream + .on('data', handleData) + .on('error', handleError); + + const closeHandler = () => { + eventStream + .removeListener('data', handleData) + .removeListener('error', handleError) + .removeListener('closed', handleError); + logger.info(`Closed event stream for ${hostName}`); + }; + + ws.on('close', closeHandler); + ws.on('error', closeHandler); + + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + logger.error("Container data error:", message); + ws.send(JSON.stringify({ + error: "Failed to fetch container data", + details: message + })); + ws.close(); + } +} \ No newline at end of file diff --git a/src/utils/dockerClient.ts b/src/utils/dockerClient.ts index 8f2718b2..469c4096 100644 --- a/src/utils/dockerClient.ts +++ b/src/utils/dockerClient.ts @@ -1,7 +1,6 @@ import Docker from "dockerode"; import fs from "fs"; import logger from "./logger"; - import { dockerConfig, target } from "../typings/dockerConfig"; function loadDockerConfig(): dockerConfig { @@ -11,16 +10,15 @@ function loadDockerConfig(): dockerConfig { logger.debug("Refreshed DockerConfig.json"); return JSON.parse(rawData) as dockerConfig; } catch (error: unknown) { - logger.error( - "Error loading dockerConfig.json: " + (error as Error).message, - ); - throw new Error("Failed to load Docker configuration"); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + throw new Error(errorMsg); } } function createDockerClient(hostConfig: target): Docker { logger.info( - `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}`, + `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}` ); return new Docker({ host: hostConfig.url, @@ -29,7 +27,7 @@ function createDockerClient(hostConfig: target): Docker { }); } -const getDockerClient = (hostName: string): Docker => { +export const getDockerClient = (hostName: string): Docker => { logger.debug(`Getting Docker Client for ${hostName}`); const config = loadDockerConfig(); const hostConfig = config.hosts.find((host) => host.name === hostName); @@ -41,5 +39,3 @@ const getDockerClient = (hostName: string): Docker => { } return createDockerClient(hostConfig); }; - -export default getDockerClient; diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts index 0af612ec..a383dc00 100644 --- a/src/utils/extractHostData.ts +++ b/src/utils/extractHostData.ts @@ -1,9 +1,54 @@ import { JsonData } from "../typings/hostData"; +import logger from "./logger"; type ComponentMap = Record; -// Export the function with type annotations -function extractRelevantData(jsonData: JsonData) { +interface RelevantData { + hostName: string; + info: { + ID: string; + Containers: number; + ContainersRunning: number; + ContainersPaused: number; + ContainersStopped: number; + Images: number; + OperatingSystem: string; + KernelVersion: string; + Architecture: string; + MemTotal: number; + NCPU: number; + }; + version: { + Components: ComponentMap; + }; +} + +function processComponents(components: unknown): ComponentMap { + try { + if (!Array.isArray(components)) return {}; + + return components.reduce((acc, component) => { + if ( + typeof component === 'object' && + component !== null && + 'Name' in component && + 'Version' in component + ) { + const { Name, Version } = component; + if (typeof Name === 'string' && typeof Version === 'string') { + acc[Name] = Version; + } + } + return acc; + }, {}); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + logger.error(`Error processing components: ${errorMessage}`); + return {}; + } +} + +export function extractRelevantData(jsonData: JsonData): RelevantData { return { hostName: jsonData.hostName, info: { @@ -20,29 +65,7 @@ function extractRelevantData(jsonData: JsonData) { NCPU: jsonData.info.NCPU, }, version: { - Components: (() => { - try { - if (!Array.isArray(jsonData?.version?.Components)) { - return {}; - } - - return jsonData.version.Components.reduce( - (acc, component) => { - if ( - typeof component?.Name === "string" && - typeof component?.Version === "string" - ) { - acc[component.Name] = component.Version; - } - return acc; - }, - {}, - ); - } catch (error) { - console.error("Error processing Components data:", error); - return {}; - } - })(), + Components: processComponents(jsonData?.version?.Components), }, }; } diff --git a/src/utils/logger.ts b/src/utils/logger.ts index 00adbdfc..2fd67bd5 100644 --- a/src/utils/logger.ts +++ b/src/utils/logger.ts @@ -1,7 +1,7 @@ import { createLogger, format, transports } from "winston"; import DailyRotateFile from "winston-daily-rotate-file"; +import { LOG_LEVEL } from "../config/variables"; -// ANSI color codes for log level customization const colors = { gray: "\x1b[90m", reset: "\x1b[0m", @@ -12,7 +12,6 @@ const colors = { blue: "\x1b[34m", }; -// Custom formatter to colorize log levels function colorizeLogLevel(level: string, levelName: string) { switch (level) { case "info": @@ -28,7 +27,7 @@ function colorizeLogLevel(level: string, levelName: string) { } } -// Filter out unwanted logs (example: Exit listeners logs) +// Filter out Exit listeners logs const filterLogs = format((info) => { if ( typeof info.message === "string" && @@ -39,9 +38,8 @@ const filterLogs = format((info) => { return info; }); -// Logger instance const logger = createLogger({ - level: "debug", + level: LOG_LEVEL, format: format.combine( filterLogs(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), @@ -56,7 +54,7 @@ const logger = createLogger({ info.level.toLowerCase(), level, ); - const message = `${colors.white}${info.message}${colors.reset}`; + const message = `${colors.white}${(info.message as string).replace(/\n|\r/g, "")}${colors.reset}`; return `${timestamp} ${levelColorized} : ${message}`; }), diff --git a/src/utils/notifications/_template.ts b/src/utils/notifications/_template.ts index 250f0950..fd5d71ed 100644 --- a/src/utils/notifications/_template.ts +++ b/src/utils/notifications/_template.ts @@ -14,7 +14,8 @@ function getTemplate(): Template | null { const data = fs.readFileSync(templatePath, "utf8"); return JSON.parse(data); } catch (error: unknown) { - logger.error("Failed to load template:", error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); return null; } } @@ -28,7 +29,8 @@ function setTemplate(newTemplate: string): void { ); logger.debug("Template updated successfully"); } catch (error: unknown) { - logger.error("Failed to update template:", error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } @@ -65,7 +67,8 @@ function renderTemplate(containerId: string): string | null { return text.replace(new RegExp(`{{${key}}}`, "g"), String(value)); }, template.text); } catch (error: unknown) { - logger.error("Failed to load containers:", error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); return null; } } diff --git a/src/utils/notifications/email.ts b/src/utils/notifications/email.ts index 4cd41a10..62b37d3a 100644 --- a/src/utils/notifications/email.ts +++ b/src/utils/notifications/email.ts @@ -47,6 +47,7 @@ export async function emailNotification(containerId: string) { try { await transporter.sendMail(mailOptions); } catch (error: unknown) { - logger.error("Error sending email:", error as Error); + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); } } diff --git a/src/utils/startServer.ts b/src/utils/startServer.ts new file mode 100644 index 00000000..7ca612f8 --- /dev/null +++ b/src/utils/startServer.ts @@ -0,0 +1,18 @@ +import { Express } from "express"; +import { Server } from 'http'; +import { startMasterNode } from "../controllers/highAvailability"; +import writeUserConf from "../config/hostsystem"; +import initFiles from "../config/initFiles"; + + +export function startServer(app: Express, server: Server, port: number) { + if (process.env.NODE_ENV === "testing") { + writeUserConf(port); + initFiles(); + } + + + server.listen(port, () => { + startMasterNode(); + }); +} \ No newline at end of file diff --git a/src/utils/swaggerDocs.ts b/src/utils/swaggerDocs.ts index 540304a7..7ed90d9d 100644 --- a/src/utils/swaggerDocs.ts +++ b/src/utils/swaggerDocs.ts @@ -1,11 +1,12 @@ import swaggerUi from "swagger-ui-express"; -import swaggerJsdoc from "swagger-jsdoc"; -import swaggerConfig from "../config/swaggerConfig"; +import { options } from "../config/swaggerConfig"; +import yaml from "yamljs"; import express from "express"; +import { SwaggerDefinition } from "swagger-jsdoc"; const swaggerDocs = (app: express.Application) => { - const specs = swaggerJsdoc(swaggerConfig); - app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(specs)); + const swaggerYaml: SwaggerDefinition = yaml.load("./src/config/swagger.yaml"); + app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerYaml, options)); }; export default swaggerDocs; diff --git a/src/utils/webSocket.ts b/src/utils/webSocket.ts new file mode 100644 index 00000000..893647e3 --- /dev/null +++ b/src/utils/webSocket.ts @@ -0,0 +1,113 @@ +import { Server } from 'http'; +import { WebSocketServer, WebSocket } from 'ws'; +import { URL } from 'url'; +import fs from 'fs'; +import logger from "./logger"; +import { streamContainerData } from './containerService'; + +export function setupWebSocket(server: Server) { + const wss = new WebSocketServer({ noServer: true }); + + server.on('upgrade', (req, socket, head) => { + logger.debug(`Received upgrade request for URL: ${req.url}`); + const baseURL = `http://${req.headers.host}/`; + const requestURL = new URL(req.url || '', baseURL); + const {pathname} = requestURL; + logger.debug(`Parsed pathname: ${pathname}`); + + // Debug log to verify path handling + logger.debug(`Handling upgrade for path: ${pathname}`); + + if (pathname === '/wss/container-data' || pathname === '/wss/server-logs') { + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit('connection', ws, req); + }); + } else { + logger.warn(`Rejected WebSocket connection to invalid path: ${pathname}`); + socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); + socket.destroy(); + } + }); + + server.on("error", (error) => { + logger.error("HTTP server error:", error); + }); + + logger.debug("WebSocket server attached to HTTP server"); + + wss.on('connection', (ws: WebSocket, req) => { + const baseURL = `http://${req.headers.host}/`; + const requestURL = new URL(req.url || '', baseURL); + const {pathname} = requestURL; + + logger.info(`WebSocket connection established to ${pathname}`); + + const handleError = (error: string) => { + ws.send(JSON.stringify({ error })); + ws.close(); + }; + + if (pathname === '/wss/container-data') { + const hostName = requestURL.searchParams.get('host'); + if (!hostName) { + handleError('Missing required host parameter'); + return; + } + streamContainerData(ws, hostName); + } else if (pathname === '/wss/server-logs') { + const logFiles = fs.readdirSync("logs/").filter(file => file.startsWith('app-')); + + if (logFiles.length === 0) { + console.error('No log files found'); + return; + } + + const sortedLogFiles = logFiles.sort((a, b) => { + const dateA = a.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; + const dateB = b.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; + + return dateB.localeCompare(dateA); + }); + + const logPath = "logs/" + sortedLogFiles[0]; + + if (!fs.existsSync(logPath)) { + handleError('Log file not found'); + logger.error(`Log file ${logPath} not found`) + return; + } + + // Read the initial content of the log file + let lastSize = fs.statSync(logPath).size; + const history = fs.readFileSync(logPath, 'utf-8'); + ws.send(JSON.stringify({ type: 'log-history', data: history })); + + // Watch the log file for changes + const watcher = fs.watch(logPath, (eventType) => { + if (eventType === 'change') { + const newSize = fs.statSync(logPath).size; + if (newSize > lastSize) { + const stream = fs.createReadStream(logPath, { + start: lastSize, + end: newSize - 1, + encoding: 'utf-8' + }); + + stream.on('data', (chunk) => { + ws.send(JSON.stringify({ type: 'log-update', data: chunk })); + }); + + lastSize = newSize; + } + } + }); + + ws.on('close', () => { + watcher.close(); + logger.info('Closed WebSocket connection for logs'); + }); + } else { + handleError('Invalid WebSocket endpoint'); + } + }); +} \ No newline at end of file diff --git a/tests/main.spec.ts b/tests/main.spec.ts deleted file mode 100644 index f9006426..00000000 --- a/tests/main.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { test, expect } from '@playwright/test'; -import ora from 'ora'; - -interface Route { - url: string; -} - -interface FrontendRoute { - url: string; - type: string; -} - -const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - -test('Swagger - Auth enable and disable', async ({ page }) => { - await page.goto('http://localhost:9876/api-docs/'); - await page.getByLabel('post /auth/enable').click(); - await page.getByRole('button', { name: 'Try it out' }).click(); - await page.getByPlaceholder('password').click(); - await page.getByPlaceholder('password').fill('1'); - await page.getByRole('button', { name: 'Execute' }).click(); - await page.getByRole('button', { name: 'Authorize' }).click(); - await page.getByLabel('Value:').click(); - await page.getByLabel('Value:').fill('1'); - await page.getByLabel('Apply credentials').click(); - await page.getByRole('button', { name: 'Close' }).click(); - await page.getByLabel('post /auth/disable').click(); - await page.getByRole('button', { name: 'Try it out' }).click(); - await page.getByRole('row', { name: 'password *required (query)', exact: true }).getByPlaceholder('password').click(); - await page.getByRole('row', { name: 'password *required (query)', exact: true }).getByPlaceholder('password').fill('1'); - await page.locator('#operations-Authentication-post_auth_disable').getByRole('button', { name: 'Execute' }).click(); -}); - -test('Return 200 status code', async ({ request }) => { - await sleep(5000); - const getRoutes: Route[] = [ - { url: 'http://localhost:9876/data/latest' }, - { url: 'http://localhost:9876/data/time/24h' }, - { url: 'http://localhost:9876/api/hosts' }, - { url: 'http://localhost:9876/api/host/Fin-2/stats' }, - { url: 'http://localhost:9876/api/containers' }, - { url: 'http://localhost:9876/api/config' }, - { url: 'http://localhost:9876/api/current-schedule' }, - { url: 'http://localhost:9876/api/frontend-config' }, - { url: 'http://localhost:9876/api/status' }, - { url: 'http://localhost:9876/ha/config' }, - { url: 'http://localhost:9876/ha/prepare-sync' }, - { url: 'http://localhost:9876/notification-service/get-template' } - ]; - - for (const { url } of getRoutes) { - const spinner = ora(`Checking: ${url}`).start(); - const response = await request.get(`${url}`); - await sleep(1000); - if (response.status() === 200) { - spinner.succeed(`Checked: ${url}`); - } else { - spinner.fail(`Failed: ${url}`); - } - expect(response.status()).toBe(200); - } - - const putRoutes: Route[] = [ - { url: 'http://localhost:9876/conf/addHost?name=test&url=localhost&port=2375' }, - { url: 'http://localhost:9876/conf/scheduler?interval=300s' } - ]; - - for (const { url } of putRoutes) { - const spinner = ora(`Checking: ${url}`).start(); - const response = await request.put(`${url}`); - await sleep(1000); - if (response.status() === 200) { - spinner.succeed(`Checked: ${url}`); - } else { - spinner.fail(`Failed: ${url}`); - } - expect(response.status()).toBe(200); - } - - const data = { text: "{{name}} ({{id}}) on {{hostName}} is {{state}}." }; - - const spinner = ora('Checking: http://localhost:9876/notification-service/set-template').start(); - const response = await request.post('http://localhost:9876/notification-service/set-template', { data }); - await sleep(1000); - if (response.status() === 200) { - spinner.succeed('Checked: http://localhost:9876/notification-service/set-template'); - } else { - spinner.fail('Failed: http://localhost:9876/notification-service/set-template'); - } - expect(response.status()).toBe(200); - - // Remove test host: - const deleteSpinner = ora('Removing test host').start(); - await request.delete('http://localhost:9876/conf/removeHost?hostName=test'); - await sleep(1000); - deleteSpinner.succeed('Removed test host'); - - const frontendRoutes: FrontendRoute[] = [ - { url: 'http://localhost:9876/frontend/tag/test/test', type: "post" }, - { url: 'http://localhost:9876/frontend/pin/test', type: "post" }, - { url: 'http://localhost:9876/frontend/add-link/test/https%3A%2F%2Fexample.com', type: "post" }, - { url: 'http://localhost:9876/frontend/add-icon/test/test.png/true', type: "post" }, - { url: 'http://localhost:9876/frontend/hide/test', type: "delete" }, - { url: 'http://localhost:9876/frontend/remove-tag/test/test', type: "delete" }, - { url: 'http://localhost:9876/frontend/remove-link/test', type: "delete" }, - { url: 'http://localhost:9876/frontend/show/test', type: "post" }, - { url: 'http://localhost:9876/frontend/remove-icon/test', type: "delete" }, - { url: 'http://localhost:9876/frontend/unpin/test', type: "delete" } - ]; - - for (const { url, type } of frontendRoutes) { - const spinner = ora(`Checking: ${url}`).start(); - let response; - if (type === "post") { - response = await request.post(`${url}`); - } else if (type === "put") { - response = await request.put(`${url}`); - } else if (type === "delete") { - response = await request.delete(`${url}`); - } else { - throw new Error(`Unsupported request type: ${type}`); - } - await sleep(1000); - if (response.status() === 200) { - spinner.succeed(`Checked: ${url}`); - } else { - spinner.fail(`Failed: ${url}`); - } - expect(response.status()).toBe(200); - } -}); diff --git a/tsconfig.json b/tsconfig.json index 4af6b1d1..c4f6f4c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,6 +12,6 @@ }, "$schema": "https://json.schemastore.org/tsconfig", "display": "Recommended", - "include": ["src/**/*"], + "include": ["src/**/*", "**/*.d.ts", "__tests__/**/*"], "exclude": ["node_modules", "**/*.spec.ts"] } From ad79836fd0bb0a6047a71fd3d613fb8378bec997 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 9 Feb 2025 13:39:26 +0000 Subject: [PATCH 117/135] Automatically added GitHub issue links to TODOs --- src/handlers/graph.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts index 53e245f7..bf33d222 100644 --- a/src/handlers/graph.ts +++ b/src/handlers/graph.ts @@ -101,6 +101,7 @@ async function generateGraphFiles( for (const [hostName, containers] of Object.entries(allContainerData)) { if ("error" in containers) { // TODO: make error'ed hosts better + // Issue URL: https://github.com/Its4Nik/DockStatAPI/issues/32 graphElements.push({ data: { id: hostName, From 1aa44c6efda580d02004314b48e6abb417f95075 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 9 Feb 2025 20:06:46 +0100 Subject: [PATCH 118/135] Fix: Websocket logic adjustments --- src/handlers/graph.ts | 4 ++-- src/utils/webSocket.ts | 33 ++++++++++++++------------------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts index bf33d222..6212adf5 100644 --- a/src/handlers/graph.ts +++ b/src/handlers/graph.ts @@ -77,14 +77,14 @@ async function renderGraphToImage( } } - throw new Error(`Graph rendering failed: ${errorMessage}`); + throw new Error(`Graph rendering failed - ${errorMessage}`); } finally { if (browser) { await browser.close().catch(() => { }); } } - logger.info(`Graph rendered and image saved to: ${outputImagePath}`); + logger.info(`Graph rendered and image saved to - ${outputImagePath}`); } async function generateGraphFiles( diff --git a/src/utils/webSocket.ts b/src/utils/webSocket.ts index 893647e3..0d412297 100644 --- a/src/utils/webSocket.ts +++ b/src/utils/webSocket.ts @@ -12,7 +12,7 @@ export function setupWebSocket(server: Server) { logger.debug(`Received upgrade request for URL: ${req.url}`); const baseURL = `http://${req.headers.host}/`; const requestURL = new URL(req.url || '', baseURL); - const {pathname} = requestURL; + const { pathname } = requestURL; logger.debug(`Parsed pathname: ${pathname}`); // Debug log to verify path handling @@ -38,7 +38,7 @@ export function setupWebSocket(server: Server) { wss.on('connection', (ws: WebSocket, req) => { const baseURL = `http://${req.headers.host}/`; const requestURL = new URL(req.url || '', baseURL); - const {pathname} = requestURL; + const { pathname } = requestURL; logger.info(`WebSocket connection established to ${pathname}`); @@ -83,27 +83,22 @@ export function setupWebSocket(server: Server) { ws.send(JSON.stringify({ type: 'log-history', data: history })); // Watch the log file for changes - const watcher = fs.watch(logPath, (eventType) => { - if (eventType === 'change') { - const newSize = fs.statSync(logPath).size; - if (newSize > lastSize) { - const stream = fs.createReadStream(logPath, { - start: lastSize, - end: newSize - 1, - encoding: 'utf-8' - }); - - stream.on('data', (chunk) => { - ws.send(JSON.stringify({ type: 'log-update', data: chunk })); - }); - - lastSize = newSize; - } + const watcher = fs.watchFile(logPath, { interval: 1000 }, (curr, prev) => { + if (curr.size > prev.size) { + const stream = fs.createReadStream(logPath, { + start: prev.size, + end: curr.size - 1, + encoding: 'utf-8' + }); + + stream.on('data', (chunk) => { + ws.send(JSON.stringify({ type: 'log-update', data: chunk })); + }); } }); ws.on('close', () => { - watcher.close(); + watcher.removeAllListeners(); logger.info('Closed WebSocket connection for logs'); }); } else { From 915cc33fe4d9431b1a3b5165b4d97eb44da5e8e9 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 9 Feb 2025 20:09:18 +0100 Subject: [PATCH 119/135] Fix: Make Linter happy --- src/utils/webSocket.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/webSocket.ts b/src/utils/webSocket.ts index 0d412297..cabf3be7 100644 --- a/src/utils/webSocket.ts +++ b/src/utils/webSocket.ts @@ -78,7 +78,6 @@ export function setupWebSocket(server: Server) { } // Read the initial content of the log file - let lastSize = fs.statSync(logPath).size; const history = fs.readFileSync(logPath, 'utf-8'); ws.send(JSON.stringify({ type: 'log-history', data: history })); From 63c396e571789bc1e823a0bfdfbc4281ec4d35fc Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 11 Feb 2025 13:25:19 +0100 Subject: [PATCH 120/135] Change: Graph generation logic changed! --- src/handlers/graph.ts | 295 +++++++--------------------------- src/routes/graphs/routes.ts | 14 ++ src/utils/containerService.ts | 60 +++---- 3 files changed, 108 insertions(+), 261 deletions(-) diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts index 6212adf5..61607c14 100644 --- a/src/handlers/graph.ts +++ b/src/handlers/graph.ts @@ -2,257 +2,84 @@ import cytoscape from "cytoscape"; import logger from "../utils/logger"; import { AllContainerData, ContainerData } from "./../typings/dockerConfig"; import { atomicWrite } from "../utils/atomicWrite"; -import { rateLimitedReadFile } from "../utils/rateLimitFS"; const CACHE_DIR_JSON = "./src/data/graph.json"; -const CACHE_DIR_HTML = "./src/data/graph.html"; -const _assets = "./src/utils/assets"; -const serverSvg = `${_assets}/server-icon.svg`; -const containerSvg = `${_assets}/container-icon.svg`; -const pngPath = "./src/data/graph.png"; -async function getPathData(path: string) { - try { - return await rateLimitedReadFile(path); - - } catch (error: unknown) { - const errorMsg = error instanceof Error ? error.message : String(error); - logger.error(errorMsg); - return false; - } -} - -async function renderGraphToImage( - htmlContent: string, - outputImagePath: string, -): Promise { - let puppeteer; - try { - puppeteer = await import("puppeteer"); - } catch (error) { - logger.error("Puppeteer is not installed. Please install it to generate images."); - throw new Error(`Puppeteer is not installed (${error})`); - } - - let browser; - try { - browser = await puppeteer.default.launch({ - headless: "shell", - args: ["--disable-setuid-sandbox", "--no-sandbox"], - executablePath: process.env.PUPPETEER_EXECUTABLE_PATH, - }); - - const page = await browser.newPage(); - await page.setContent(htmlContent, { waitUntil: "networkidle0" }); - await page.waitForSelector("#cy", { visible: true, timeout: 15000 }); - - await page.waitForFunction( - () => { - const cyElement = document.querySelector("#cy"); - return cyElement ? cyElement.children.length > 0 : false; - }, - { timeout: 10000 } - ); - - await page.screenshot({ - path: outputImagePath, - type: outputImagePath.endsWith(".jpg") ? "jpeg" : "png", - fullPage: true, - captureBeyondViewport: true, - }); - } catch (error: unknown) { - let errorMessage = "Unknown error occurred during browser operation"; - - if (error instanceof Error) { - errorMessage = error.message; - - // Detect common dependency errors - if (errorMessage.includes("libnss3") || errorMessage.includes("libxcb")) { - errorMessage = `❗ Missing system dependencies (libnss3)`; - } - - // Detect Chrome not found errors - if (errorMessage.includes("Failed to launch")) { - errorMessage = `❗ Chrome not found!`; - } - } - - throw new Error(`Graph rendering failed - ${errorMessage}`); - } finally { - if (browser) { - await browser.close().catch(() => { }); - } - } - - logger.info(`Graph rendered and image saved to - ${outputImagePath}`); -} - -async function generateGraphFiles( +async function generateGraphJSON( allContainerData: AllContainerData, ): Promise { - if (process.env.CI === "true") { - logger.warn("Running inside a CI/CD Action, wont generated graphs"); - return false; - } else { - try { - logger.info("generateGraphFiles >>> Starting generation"); - const graphElements: cytoscape.ElementDefinition[] = []; - - for (const [hostName, containers] of Object.entries(allContainerData)) { - if ("error" in containers) { - // TODO: make error'ed hosts better - // Issue URL: https://github.com/Its4Nik/DockStatAPI/issues/32 - graphElements.push({ + try { + logger.info("generateGraphJSON >>> Starting generation"); + + // Define the new JSON structure + const graphData = { + nodes: [] as cytoscape.ElementDefinition[], + edges: [] as cytoscape.ElementDefinition[], + }; + + for (const [hostName, containers] of Object.entries(allContainerData)) { + if ("error" in containers) { + graphData.nodes.push({ + data: { + id: hostName, + label: `Host: ${hostName} Error: ${containers.error}`, + type: "server", + error: true, + }, + }); + } else { + const containerList = containers as ContainerData[]; + + // Host node with container count and metadata + graphData.nodes.push({ + data: { + id: hostName, + label: `${hostName}\n${containerList.length} Containers`, + type: "server", + hostName, + containerCount: containerList.length, + }, + }); + + for (const container of containerList) { + const { id, ...otherContainerProps } = container; + + graphData.nodes.push({ data: { - id: hostName, - label: `Host: ${hostName} Error: ${containers.error}`, - type: "server", + id: id, + label: `${container.name}\n${container.state.toUpperCase()}`, + type: "container", + parent: hostName, + ...otherContainerProps, }, }); - } else { - const containerList = containers as ContainerData[]; - // host node with container count - graphElements.push({ + // Edge between host and container + graphData.edges.push({ data: { - id: hostName, - label: `${hostName} - ${containerList.length} Containers`, - type: "server", + id: `${hostName}-${container.id}`, + source: hostName, + target: container.id, + connectionType: "host-container", }, }); - - for (const container of containerList) { - // container node - graphElements.push({ - data: { - id: container.id, - label: `${container.name} (${container.state})`, - type: "container", - }, - }); - - // edge between host and container - graphElements.push({ - data: { - source: hostName, - target: container.id, - }, - }); - } } } - - atomicWrite(CACHE_DIR_JSON, JSON.stringify(graphElements, null, 2)); - - const htmlContent = ` - - - - - - Cytoscape Graph - - - - -
- - - - `; - - atomicWrite(CACHE_DIR_HTML, htmlContent); - await renderGraphToImage(htmlContent, pngPath) - .then(() => logger.debug("HTML converted to image successfully!")) - .catch((err) => logger.error("Error:", err)); - - logger.info("generateGraphFiles <<< Files generated successfully"); - return true; - } catch (error: unknown) { - const errorMsg = error instanceof Error ? error.message : String(error); - logger.error(errorMsg); - return false; } + + // Write the new structured JSON to file + atomicWrite(CACHE_DIR_JSON, JSON.stringify(graphData, null, 2)); + logger.info("generateGraphJSON <<< JSON file generated successfully"); + return true; + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + logger.error(errorMsg); + return false; } } -function getGraphFilePaths() { - return { json: CACHE_DIR_JSON, html: CACHE_DIR_HTML }; +function getGraphFilePath() { + return { json: CACHE_DIR_JSON }; } -export { generateGraphFiles, getGraphFilePaths }; +export { generateGraphJSON, getGraphFilePath }; diff --git a/src/routes/graphs/routes.ts b/src/routes/graphs/routes.ts index db532058..bf6c8a99 100644 --- a/src/routes/graphs/routes.ts +++ b/src/routes/graphs/routes.ts @@ -1,6 +1,7 @@ import { Request, Response, Router } from "express"; import { createResponseHandler } from "../../handlers/response"; import path from "path"; +import { rateLimitedReadFile } from "../../utils/rateLimitFS"; const router = Router(); router.get("/", async (req: Request, res: Response) => { @@ -28,4 +29,17 @@ router.get("/image", async (req: Request, res: Response) => { } }); +router.get("/json", async (req: Request, res: Response) => { + const ResponseHandler = createResponseHandler(res); + try { + const data = await rateLimitedReadFile( + path.join(__dirname, "/../../.." + "/src/data/graph.json"), + ); + return ResponseHandler.rawData(data, "Graph JSON fetched"); + } catch (error: unknown) { + const errorMsg = error instanceof Error ? error.message : String(error); + return ResponseHandler.critical(errorMsg); + } +}); + export default router; diff --git a/src/utils/containerService.ts b/src/utils/containerService.ts index 86dc2d38..0bb0a4e7 100644 --- a/src/utils/containerService.ts +++ b/src/utils/containerService.ts @@ -1,18 +1,18 @@ import logger from "./logger"; -import { ContainerInfo, } from "dockerode"; +import { ContainerInfo } from "dockerode"; import { getDockerClient } from "./dockerClient"; import fs from "fs"; import { atomicWrite } from "./atomicWrite"; const configPath = "./src/data/dockerConfig.json"; import { AllContainerData, HostConfig } from "../typings/dockerConfig"; -import { generateGraphFiles } from "../handlers/graph"; +import { generateGraphJSON } from "../handlers/graph"; import { WebSocket } from "ws"; export function loadConfig() { try { if (!fs.existsSync(configPath)) { logger.warn( - `Config file not found. Creating an empty file at ${configPath}` + `Config file not found. Creating an empty file at ${configPath}`, ); atomicWrite(configPath, JSON.stringify({ hosts: [] }, null, 2)); } @@ -37,7 +37,9 @@ export async function fetchContainersForHost(hostName: string) { try { const docker = getDockerClient(hostName); - const containers: ContainerInfo[] = await docker.listContainers({ all: true }); + const containers: ContainerInfo[] = await docker.listContainers({ + all: true, + }); return await Promise.all( containers.map(async (container) => { @@ -56,7 +58,8 @@ export async function fetchContainersForHost(hostName: string) { containerStats.precpu_stats.system_cpu_usage; const cpuUsage = systemCpuDelta > 0 - ? (cpuDelta / systemCpuDelta) * containerStats.cpu_stats.online_cpus + ? (cpuDelta / systemCpuDelta) * + containerStats.cpu_stats.online_cpus : 0; return { @@ -90,7 +93,7 @@ export async function fetchContainersForHost(hostName: string) { networkMode: "unknown", }; } - }) + }), ); } catch (error) { logger.error(`Error fetching containers for ${hostName}: ${error}`); @@ -105,16 +108,18 @@ export async function fetchAllContainers(): Promise { await Promise.all( config.hosts.map(async (hostConfig: HostConfig) => { try { - allContainerData[hostConfig.name] = await fetchContainersForHost(hostConfig.name); + allContainerData[hostConfig.name] = await fetchContainersForHost( + hostConfig.name, + ); } catch (error) { allContainerData[hostConfig.name] = { - error: `Error fetching containers: ${error instanceof Error ? error.message : String(error)}` + error: `Error fetching containers: ${error instanceof Error ? error.message : String(error)}`, }; } - }) + }), ); - generateGraphFiles(allContainerData); + generateGraphJSON(allContainerData); return allContainerData; } @@ -127,12 +132,14 @@ export async function streamContainerData(ws: WebSocket, hostName: string) { const eventStream = await docker.getEvents(); // eslint-disable-next-line - if (!(eventStream instanceof require('stream').Readable)) { - throw new Error('Failed to get valid event stream'); + if (!(eventStream instanceof require("stream").Readable)) { + throw new Error("Failed to get valid event stream"); } const handleData = (chunk: Buffer) => { - ws.send(JSON.stringify({ type: "container-event", data: chunk.toString() })); + ws.send( + JSON.stringify({ type: "container-event", data: chunk.toString() }), + ); }; const handleError = (err: Error) => { @@ -140,28 +147,27 @@ export async function streamContainerData(ws: WebSocket, hostName: string) { ws.close(); }; - eventStream - .on('data', handleData) - .on('error', handleError); + eventStream.on("data", handleData).on("error", handleError); const closeHandler = () => { eventStream - .removeListener('data', handleData) - .removeListener('error', handleError) - .removeListener('closed', handleError); + .removeListener("data", handleData) + .removeListener("error", handleError) + .removeListener("closed", handleError); logger.info(`Closed event stream for ${hostName}`); }; - ws.on('close', closeHandler); - ws.on('error', closeHandler); - + ws.on("close", closeHandler); + ws.on("error", closeHandler); } catch (error) { const message = error instanceof Error ? error.message : String(error); logger.error("Container data error:", message); - ws.send(JSON.stringify({ - error: "Failed to fetch container data", - details: message - })); + ws.send( + JSON.stringify({ + error: "Failed to fetch container data", + details: message, + }), + ); ws.close(); } -} \ No newline at end of file +} From 3f6792325b5f7a2e22b72d25ee0671ec43f9a5ee Mon Sep 17 00:00:00 2001 From: ItsNik Date: Wed, 12 Feb 2025 13:14:27 +0100 Subject: [PATCH 121/135] Fix: Code styling --- .github/workflows/build-image.yaml | 2 +- .github/workflows/validation.yaml | 2 +- CREDITS.md | 43 +++--- TODO.md | 4 +- __tests__/auth.spec.ts | 4 +- __tests__/config.spec.ts | 2 +- __tests__/database.spec.ts | 2 +- __tests__/frontend.spec.ts | 4 +- __tests__/getters.spec.ts | 2 +- src/handlers/graph.ts | 1 - src/handlers/stack.ts | 2 +- src/utils/dockerClient.ts | 2 +- src/utils/extractHostData.ts | 8 +- src/utils/startServer.ts | 6 +- src/utils/webSocket.ts | 208 +++++++++++++++-------------- 15 files changed, 148 insertions(+), 144 deletions(-) diff --git a/.github/workflows/build-image.yaml b/.github/workflows/build-image.yaml index bbb4875d..9d43ff17 100644 --- a/.github/workflows/build-image.yaml +++ b/.github/workflows/build-image.yaml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - + - name: Set up QEMU uses: docker/setup-qemu-action@v3 diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 7e2b685c..52797fcc 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -1,6 +1,6 @@ name: "Run all tests" -on: +on: push: release: types: diff --git a/CREDITS.md b/CREDITS.md index 50b66abb..6dd2d893 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -20,35 +20,37 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | ------------------------------------ | ------------------------------------------------------------------------ | -------------------- | | @ampproject/remapping@2.3.0 | https://github.com/ampproject/remapping | Justin Ridgewell | | @balena/dockerignore@1.0.2 | https://github.com/balena-io-modules/dockerignore | N/A | -| @eslint/config-array@0.19.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/core@0.9.1 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/object-schema@2.1.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | -| @eslint/plugin-kit@0.2.4 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/config-array@0.19.2 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.10.0 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/core@0.11.0 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/object-schema@2.1.6 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @eslint/plugin-kit@0.2.5 | https://github.com/eslint/rewrite | Nicholas C. Zakas | +| @grpc/grpc-js@1.12.6 | https://github.com/grpc/grpc-node/tree/master/packages/grpc-js | Google Inc. | +| @grpc/proto-loader@0.7.13 | https://github.com/grpc/grpc-node | Google Inc. | | @humanfs/core@0.19.1 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | | @humanfs/node@0.16.6 | https://github.com/humanwhocodes/humanfs | Nicholas C. Zakas | | @humanwhocodes/module-importer@1.0.1 | https://github.com/humanwhocodes/module-importer | Nicholas C. Zaks | | @humanwhocodes/retry@0.3.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | | @humanwhocodes/retry@0.4.1 | https://github.com/humanwhocodes/retry | Nicholas C. Zaks | -| @puppeteer/browsers@2.7.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/browsers | The Chromium Authors | +| @puppeteer/browsers@2.7.1 | https://github.com/puppeteer/puppeteer/tree/main/packages/browsers | The Chromium Authors | | @scarf/scarf@1.4.0 | https://github.com/scarf-sh/scarf-js | Scarf Systems | | @sigstore/bundle@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | | @sigstore/core@2.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | -| @sigstore/protobuf-specs@0.3.2 | https://github.com/sigstore/protobuf-specs | bdehamer@github.com | +| @sigstore/protobuf-specs@0.3.3 | https://github.com/sigstore/protobuf-specs | bdehamer@github.com | | @sigstore/sign@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | | @sigstore/tuf@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | | @sigstore/verify@2.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | | b4a@1.6.7 | https://github.com/holepunchto/b4a | Holepunch | | bare-events@2.5.4 | https://github.com/holepunchto/bare-events | Holepunch | -| bare-fs@2.3.5 | https://github.com/holepunchto/bare-fs | Holepunch | -| bare-os@2.4.4 | https://github.com/holepunchto/bare-os | Holepunch | -| bare-path@2.1.3 | https://github.com/holepunchto/bare-path | Holepunch | -| bare-stream@2.6.1 | https://github.com/holepunchto/bare-stream | Holepunch | +| bare-fs@4.0.1 | https://github.com/holepunchto/bare-fs | Holepunch | +| bare-os@3.4.0 | https://github.com/holepunchto/bare-os | Holepunch | +| bare-path@3.0.0 | https://github.com/holepunchto/bare-path | Holepunch | +| bare-stream@2.6.5 | https://github.com/holepunchto/bare-stream | Holepunch | | bser@2.1.1 | https://github.com/facebook/watchman | Wez Furlong | -| chromium-bidi@0.11.0 | https://github.com/GoogleChromeLabs/chromium-bidi | The Chromium Authors | -| chromium-bidi@0.12.0 | https://github.com/GoogleChromeLabs/chromium-bidi | The Chromium Authors | +| chromium-bidi@1.2.0 | https://github.com/GoogleChromeLabs/chromium-bidi | The Chromium Authors | | detect-libc@2.0.3 | https://github.com/lovell/detect-libc | Lovell Fuller | -| docker-modem@5.0.3 | https://github.com/apocas/docker-modem | Pedro Dias | -| dockerode@4.0.2 | https://github.com/apocas/dockerode | Pedro Dias | +| docker-modem@5.0.6 | https://github.com/apocas/docker-modem | Pedro Dias | +| dockerode@4.0.4 | https://github.com/apocas/dockerode | Pedro Dias | | ejs@3.1.10 | https://github.com/mde/ejs | Matthew Eernisse | | eslint-visitor-keys@3.4.3 | https://github.com/eslint/eslint-visitor-keys | Toru Nagashima | | eslint-visitor-keys@4.2.0 | https://github.com/eslint/js | Toru Nagashima | @@ -57,14 +59,15 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | filelist@1.0.4 | https://github.com/mde/filelist | Matthew Eernisse | | human-signals@2.1.0 | https://github.com/ehmicky/human-signals | ehmicky | | jake@10.9.2 | https://github.com/jakejs/jake | Matthew Eernisse | -| puppeteer-core@24.0.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core | The Chromium Authors | -| puppeteer@24.0.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer | The Chromium Authors | +| long@5.2.4 | https://github.com/dcodeIO/long.js | Daniel Wirtz | +| puppeteer-core@24.2.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer-core | The Chromium Authors | +| puppeteer@24.2.0 | https://github.com/puppeteer/puppeteer/tree/main/packages/puppeteer | The Chromium Authors | | sigstore@3.0.0 | https://github.com/sigstore/sigstore-js | bdehamer@github.com | | spdx-correct@3.2.0 | https://github.com/jslicense/spdx-correct.js | N/A | -| swagger-ui-dist@5.18.2 | https://github.com/swagger-api/swagger-ui | N/A | +| swagger-ui-dist@5.18.3 | https://github.com/swagger-api/swagger-ui | N/A | | text-decoder@1.2.3 | https://github.com/holepunchto/text-decoder | Holepunch | | tunnel-agent@0.6.0 | https://github.com/mikeal/tunnel-agent | Mikeal Rogers | -| typescript@5.7.2 | https://github.com/microsoft/TypeScript | Microsoft Corp. | +| typescript@5.7.3 | https://github.com/microsoft/TypeScript | Microsoft Corp. | | validate-npm-package-license@3.0.4 | https://github.com/kemitchell/validate-npm-package-license.js | Kyle E. Mitchell | | walker@1.0.8 | https://github.com/daaku/nodejs-walker | Naitik Shah | @@ -72,7 +75,7 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | Name | Repository | Publisher | | ---------- | -------------------------- | ----------- | -| npm@11.0.0 | https://github.com/npm/cli | GitHub Inc. | +| npm@11.1.0 | https://github.com/npm/cli | GitHub Inc. | ### License: BlueOak-1.0.0 @@ -94,7 +97,7 @@ This file shows all npm packages used in DockStatAPI (also Dev packages) | Name | Repository | Publisher | | ------------------------- | -------------------------------------------- | ---------- | -| caniuse-lite@1.0.30001690 | https://github.com/browserslist/caniuse-lite | Ben Briggs | +| caniuse-lite@1.0.30001698 | https://github.com/browserslist/caniuse-lite | Ben Briggs | ### License: Python-2.0 diff --git a/TODO.md b/TODO.md index 44a128d3..b850ba72 100644 --- a/TODO.md +++ b/TODO.md @@ -7,12 +7,12 @@ - [x] Structure code differently - [x] Write new README and make the docs better - [x] Update more files to correct TS syntax => remove "any" -- [X] Websockets +- [x] Websockets - [x] Better /api/status endpoint with connection status of each host - [x] Update notification service - [x] Adjust process.env variables since they don't really work as expected (See [commit](https://github.com/Its4Nik/dockstatapi/pull/21/commits/a03b58c7a17e269f46216df5492e18d008774961)) - [ ] Better project structure - [x] Update logging => Better errors - [x] Update json responses -- [X] Swagger update +- [x] Swagger update - [ ] Edge case testing diff --git a/__tests__/auth.spec.ts b/__tests__/auth.spec.ts index bcf0eb21..84c5f04a 100644 --- a/__tests__/auth.spec.ts +++ b/__tests__/auth.spec.ts @@ -1,5 +1,5 @@ export const testPass = "123456789"; -import { Server } from 'http'; +import { Server } from "http"; import supertest from "supertest"; import { startServer } from "../src/utils/startServer"; import app from "../src/server"; @@ -35,4 +35,4 @@ describe("Authentication", () => { expect(res.status).toEqual(200); expect(res.type).toEqual(expect.stringContaining("json")); }); -}); \ No newline at end of file +}); diff --git a/__tests__/config.spec.ts b/__tests__/config.spec.ts index d6356004..2650e9ed 100644 --- a/__tests__/config.spec.ts +++ b/__tests__/config.spec.ts @@ -1,7 +1,7 @@ import supertest from "supertest"; import { startServer } from "../src/utils/startServer"; import app from "../src/server"; -import { Server } from 'http'; +import { Server } from "http"; const port = 13002; const server = new Server(app); diff --git a/__tests__/database.spec.ts b/__tests__/database.spec.ts index c0c46c1b..55102ce9 100644 --- a/__tests__/database.spec.ts +++ b/__tests__/database.spec.ts @@ -1,7 +1,7 @@ import supertest from "supertest"; import { startServer } from "../src/utils/startServer"; import app from "../src/server"; -import { Server } from 'http'; +import { Server } from "http"; const port = 13003; const server = new Server(app); diff --git a/__tests__/frontend.spec.ts b/__tests__/frontend.spec.ts index 753b98da..af25adc5 100644 --- a/__tests__/frontend.spec.ts +++ b/__tests__/frontend.spec.ts @@ -1,7 +1,7 @@ import supertest from "supertest"; import { startServer } from "../src/utils/startServer"; import app from "../src/server"; -import { Server } from 'http'; +import { Server } from "http"; const port = 13004; const server = new Server(app); @@ -29,8 +29,6 @@ const verifiedResponse = [ }, ]; - - describe("Test frontend specific configurations", () => { it( "Setup the configuration file", diff --git a/__tests__/getters.spec.ts b/__tests__/getters.spec.ts index 3ba5950b..f951f42a 100644 --- a/__tests__/getters.spec.ts +++ b/__tests__/getters.spec.ts @@ -2,7 +2,7 @@ import { createPreviousResponse } from "./util/previousResponse"; import supertest from "supertest"; import { startServer } from "../src/utils/startServer"; import app from "../src/server"; -import { Server } from 'http'; +import { Server } from "http"; const port = 13005; const server = new Server(app); diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts index 61607c14..587d5760 100644 --- a/src/handlers/graph.ts +++ b/src/handlers/graph.ts @@ -49,7 +49,6 @@ async function generateGraphJSON( id: id, label: `${container.name}\n${container.state.toUpperCase()}`, type: "container", - parent: hostName, ...otherContainerProps, }, }); diff --git a/src/handlers/stack.ts b/src/handlers/stack.ts index e87b533f..b3daa0f8 100644 --- a/src/handlers/stack.ts +++ b/src/handlers/stack.ts @@ -107,7 +107,7 @@ class StackHandler { async stackCompose(req: Request, res: Response) { const ResponseHandler = createResponseHandler(res); try { - const {name} = req.params; + const { name } = req.params; return ResponseHandler.rawData( await getStackCompose(name), "Stack compose fetched", diff --git a/src/utils/dockerClient.ts b/src/utils/dockerClient.ts index 469c4096..ff770888 100644 --- a/src/utils/dockerClient.ts +++ b/src/utils/dockerClient.ts @@ -18,7 +18,7 @@ function loadDockerConfig(): dockerConfig { function createDockerClient(hostConfig: target): Docker { logger.info( - `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}` + `Creating Docker client for host: ${hostConfig.url} on port: ${hostConfig.port || 2375}`, ); return new Docker({ host: hostConfig.url, diff --git a/src/utils/extractHostData.ts b/src/utils/extractHostData.ts index a383dc00..992f9638 100644 --- a/src/utils/extractHostData.ts +++ b/src/utils/extractHostData.ts @@ -29,13 +29,13 @@ function processComponents(components: unknown): ComponentMap { return components.reduce((acc, component) => { if ( - typeof component === 'object' && + typeof component === "object" && component !== null && - 'Name' in component && - 'Version' in component + "Name" in component && + "Version" in component ) { const { Name, Version } = component; - if (typeof Name === 'string' && typeof Version === 'string') { + if (typeof Name === "string" && typeof Version === "string") { acc[Name] = Version; } } diff --git a/src/utils/startServer.ts b/src/utils/startServer.ts index 7ca612f8..52dcc256 100644 --- a/src/utils/startServer.ts +++ b/src/utils/startServer.ts @@ -1,18 +1,16 @@ import { Express } from "express"; -import { Server } from 'http'; +import { Server } from "http"; import { startMasterNode } from "../controllers/highAvailability"; import writeUserConf from "../config/hostsystem"; import initFiles from "../config/initFiles"; - export function startServer(app: Express, server: Server, port: number) { if (process.env.NODE_ENV === "testing") { writeUserConf(port); initFiles(); } - server.listen(port, () => { startMasterNode(); }); -} \ No newline at end of file +} diff --git a/src/utils/webSocket.ts b/src/utils/webSocket.ts index cabf3be7..66d1f74b 100644 --- a/src/utils/webSocket.ts +++ b/src/utils/webSocket.ts @@ -1,107 +1,113 @@ -import { Server } from 'http'; -import { WebSocketServer, WebSocket } from 'ws'; -import { URL } from 'url'; -import fs from 'fs'; +import { Server } from "http"; +import { WebSocketServer, WebSocket } from "ws"; +import { URL } from "url"; +import fs from "fs"; import logger from "./logger"; -import { streamContainerData } from './containerService'; +import { streamContainerData } from "./containerService"; export function setupWebSocket(server: Server) { - const wss = new WebSocketServer({ noServer: true }); - - server.on('upgrade', (req, socket, head) => { - logger.debug(`Received upgrade request for URL: ${req.url}`); - const baseURL = `http://${req.headers.host}/`; - const requestURL = new URL(req.url || '', baseURL); - const { pathname } = requestURL; - logger.debug(`Parsed pathname: ${pathname}`); - - // Debug log to verify path handling - logger.debug(`Handling upgrade for path: ${pathname}`); - - if (pathname === '/wss/container-data' || pathname === '/wss/server-logs') { - wss.handleUpgrade(req, socket, head, (ws) => { - wss.emit('connection', ws, req); - }); - } else { - logger.warn(`Rejected WebSocket connection to invalid path: ${pathname}`); - socket.write('HTTP/1.1 404 Not Found\r\n\r\n'); - socket.destroy(); - } - }); - - server.on("error", (error) => { - logger.error("HTTP server error:", error); - }); - - logger.debug("WebSocket server attached to HTTP server"); - - wss.on('connection', (ws: WebSocket, req) => { - const baseURL = `http://${req.headers.host}/`; - const requestURL = new URL(req.url || '', baseURL); - const { pathname } = requestURL; - - logger.info(`WebSocket connection established to ${pathname}`); - - const handleError = (error: string) => { - ws.send(JSON.stringify({ error })); - ws.close(); - }; - - if (pathname === '/wss/container-data') { - const hostName = requestURL.searchParams.get('host'); - if (!hostName) { - handleError('Missing required host parameter'); - return; - } - streamContainerData(ws, hostName); - } else if (pathname === '/wss/server-logs') { - const logFiles = fs.readdirSync("logs/").filter(file => file.startsWith('app-')); - - if (logFiles.length === 0) { - console.error('No log files found'); - return; - } - - const sortedLogFiles = logFiles.sort((a, b) => { - const dateA = a.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; - const dateB = b.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; - - return dateB.localeCompare(dateA); - }); - - const logPath = "logs/" + sortedLogFiles[0]; - - if (!fs.existsSync(logPath)) { - handleError('Log file not found'); - logger.error(`Log file ${logPath} not found`) - return; - } - - // Read the initial content of the log file - const history = fs.readFileSync(logPath, 'utf-8'); - ws.send(JSON.stringify({ type: 'log-history', data: history })); - - // Watch the log file for changes - const watcher = fs.watchFile(logPath, { interval: 1000 }, (curr, prev) => { - if (curr.size > prev.size) { - const stream = fs.createReadStream(logPath, { - start: prev.size, - end: curr.size - 1, - encoding: 'utf-8' - }); - - stream.on('data', (chunk) => { - ws.send(JSON.stringify({ type: 'log-update', data: chunk })); - }); - } + const wss = new WebSocketServer({ noServer: true }); + + server.on("upgrade", (req, socket, head) => { + logger.debug(`Received upgrade request for URL: ${req.url}`); + const baseURL = `http://${req.headers.host}/`; + const requestURL = new URL(req.url || "", baseURL); + const { pathname } = requestURL; + logger.debug(`Parsed pathname: ${pathname}`); + + // Debug log to verify path handling + logger.debug(`Handling upgrade for path: ${pathname}`); + + if (pathname === "/wss/container-data" || pathname === "/wss/server-logs") { + wss.handleUpgrade(req, socket, head, (ws) => { + wss.emit("connection", ws, req); + }); + } else { + logger.warn(`Rejected WebSocket connection to invalid path: ${pathname}`); + socket.write("HTTP/1.1 404 Not Found\r\n\r\n"); + socket.destroy(); + } + }); + + server.on("error", (error) => { + logger.error("HTTP server error:", error); + }); + + logger.debug("WebSocket server attached to HTTP server"); + + wss.on("connection", (ws: WebSocket, req) => { + const baseURL = `http://${req.headers.host}/`; + const requestURL = new URL(req.url || "", baseURL); + const { pathname } = requestURL; + + logger.info(`WebSocket connection established to ${pathname}`); + + const handleError = (error: string) => { + ws.send(JSON.stringify({ error })); + ws.close(); + }; + + if (pathname === "/wss/container-data") { + const hostName = requestURL.searchParams.get("host"); + if (!hostName) { + handleError("Missing required host parameter"); + return; + } + streamContainerData(ws, hostName); + } else if (pathname === "/wss/server-logs") { + const logFiles = fs + .readdirSync("logs/") + .filter((file) => file.startsWith("app-")); + + if (logFiles.length === 0) { + console.error("No log files found"); + return; + } + + const sortedLogFiles = logFiles.sort((a, b) => { + const dateA = a.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; + const dateB = b.match(/\d{4}-\d{2}-\d{2}/)?.[0] ?? ""; + + return dateB.localeCompare(dateA); + }); + + const logPath = "logs/" + sortedLogFiles[0]; + + if (!fs.existsSync(logPath)) { + handleError("Log file not found"); + logger.error(`Log file ${logPath} not found`); + return; + } + + // Read the initial content of the log file + const history = fs.readFileSync(logPath, "utf-8"); + ws.send(JSON.stringify({ type: "log-history", data: history })); + + // Watch the log file for changes + const watcher = fs.watchFile( + logPath, + { interval: 1000 }, + (curr, prev) => { + if (curr.size > prev.size) { + const stream = fs.createReadStream(logPath, { + start: prev.size, + end: curr.size - 1, + encoding: "utf-8", }); - ws.on('close', () => { - watcher.removeAllListeners(); - logger.info('Closed WebSocket connection for logs'); + stream.on("data", (chunk) => { + ws.send(JSON.stringify({ type: "log-update", data: chunk })); }); - } else { - handleError('Invalid WebSocket endpoint'); - } - }); -} \ No newline at end of file + } + }, + ); + + ws.on("close", () => { + watcher.removeAllListeners(); + logger.info("Closed WebSocket connection for logs"); + }); + } else { + handleError("Invalid WebSocket endpoint"); + } + }); +} From d7c80167cfc64d525454ea9ae0a6811c022a89cb Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 19:13:17 +0100 Subject: [PATCH 122/135] Fix: Remove unusable routes --- src/config/swagger.yaml | 11 ----------- src/handlers/graph.ts | 2 -- src/routes/graphs/routes.ts | 25 ------------------------- 3 files changed, 38 deletions(-) diff --git a/src/config/swagger.yaml b/src/config/swagger.yaml index 9a1d50fb..2230f73b 100644 --- a/src/config/swagger.yaml +++ b/src/config/swagger.yaml @@ -33,17 +33,6 @@ info: - Multi Arch Docker builds through docker buildx - High Availability using single master and unlimited worker nodes! -
- Your container graph - [Interactive Graph](http://localhost:9876/graph) - - [Raw image](http://localhost:9876/graph/image) - - --- - - ![Your container graph](http://localhost:9876/graph/image) -
- # 🔗 DockStatAPI v2 Documentation _⚠️ = Deprecation warning_ diff --git a/src/handlers/graph.ts b/src/handlers/graph.ts index 587d5760..12e05724 100644 --- a/src/handlers/graph.ts +++ b/src/handlers/graph.ts @@ -11,7 +11,6 @@ async function generateGraphJSON( try { logger.info("generateGraphJSON >>> Starting generation"); - // Define the new JSON structure const graphData = { nodes: [] as cytoscape.ElementDefinition[], edges: [] as cytoscape.ElementDefinition[], @@ -66,7 +65,6 @@ async function generateGraphJSON( } } - // Write the new structured JSON to file atomicWrite(CACHE_DIR_JSON, JSON.stringify(graphData, null, 2)); logger.info("generateGraphJSON <<< JSON file generated successfully"); return true; diff --git a/src/routes/graphs/routes.ts b/src/routes/graphs/routes.ts index bf6c8a99..fcaa7983 100644 --- a/src/routes/graphs/routes.ts +++ b/src/routes/graphs/routes.ts @@ -4,31 +4,6 @@ import path from "path"; import { rateLimitedReadFile } from "../../utils/rateLimitFS"; const router = Router(); -router.get("/", async (req: Request, res: Response) => { - const ResponseHandler = createResponseHandler(res); - try { - const graphPath = path.join( - __dirname, - "/../../.." + "/src/data/graph.html", - ); - return res.contentType("html").status(200).sendFile(graphPath); - } catch (error: unknown) { - const errorMsg = error instanceof Error ? error.message : String(error); - return ResponseHandler.critical(errorMsg); - } -}); - -router.get("/image", async (req: Request, res: Response) => { - const ResponseHandler = createResponseHandler(res); - try { - const graphPath = path.join(__dirname, "/../../.." + "/src/data/graph.png"); - return res.contentType("image/png").status(200).sendFile(graphPath); - } catch (error: unknown) { - const errorMsg = error instanceof Error ? error.message : String(error); - return ResponseHandler.critical(errorMsg); - } -}); - router.get("/json", async (req: Request, res: Response) => { const ResponseHandler = createResponseHandler(res); try { From 62b05740c178821753c4ef609755e3c57f133c5a Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 21:55:51 +0100 Subject: [PATCH 123/135] Fix: Add docker executable --- docker/Dockerfile-base | 5 +++-- docker/Dockerfile-dev | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index 76cec4c9..d135eaa4 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -30,8 +30,9 @@ FROM node:20-alpine AS production WORKDIR /api -RUN apk add --no-cache bash curl && \ - adduser -h /api -s /bin/bash -D dockstatapi +RUN apk add --no-cache bash curl docker-cli && \ + adduser -h /api -s /bin/bash -D dockstatapi && \ + addgroup dockstatapi docker HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost:9876/api/status || exit 1 diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index 43a42402..bcc54e42 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -30,6 +30,10 @@ FROM node:20-alpine AS production WORKDIR /api +RUN apk add --no-cache bash curl docker-cli && \ + adduser -h /api -s /bin/bash -D dockstatapi && \ + addgroup dockstatapi docker + RUN apk add --no-cache bash curl && \ adduser -h /api -s /bin/bash -D dockstatapi From 01d73071c6b6fa6ab29e39207a9aef20657ddbd5 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 22:06:49 +0100 Subject: [PATCH 124/135] Fix: Fixing user creation and docker user --- docker/Dockerfile-base | 14 +++++++------- docker/Dockerfile-dev | 17 +++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index d135eaa4..80b1ae8f 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -15,9 +15,7 @@ WORKDIR /app ENV NODE_NO_WARNINGS=1 -RUN apk add --no-cache bash - -COPY tsconfig.json environment.d.ts package*.json ./ +COPY package*.json tsconfig.json environment.d.ts ./ RUN npm install --production=false @@ -30,8 +28,9 @@ FROM node:20-alpine AS production WORKDIR /api -RUN apk add --no-cache bash curl docker-cli && \ - adduser -h /api -s /bin/bash -D dockstatapi && \ +RUN apk add --no-cache docker-cli bash curl && \ + adduser -h /api -s /bin/sh -D dockstatapi && \ + addgroup -S docker && \ addgroup dockstatapi docker HEALTHCHECK --interval=5m --timeout=3s \ @@ -42,7 +41,8 @@ COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets -RUN npm install --omit=dev +RUN npm install --omit=dev && \ + rm -rf package-lock.json node_modules/.cache COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh @@ -56,4 +56,4 @@ RUN mkdir -p /api/src/data && \ STOPSIGNAL 130 USER dockstatapi -ENTRYPOINT [ "bash", "./entrypoint.sh", "--prod" ] +ENTRYPOINT [ "sh", "./entrypoint.sh", "--prod" ] diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index bcc54e42..7b439404 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -15,9 +15,7 @@ WORKDIR /app ENV NODE_NO_WARNINGS=1 -RUN apk add --no-cache bash - -COPY tsconfig.json environment.d.ts package*.json ./ +COPY package*.json tsconfig.json environment.d.ts ./ RUN npm install --production=false @@ -30,13 +28,11 @@ FROM node:20-alpine AS production WORKDIR /api -RUN apk add --no-cache bash curl docker-cli && \ - adduser -h /api -s /bin/bash -D dockstatapi && \ +RUN apk add --no-cache docker-cli bash curl && \ + adduser -h /api -s /bin/sh -D dockstatapi && \ + addgroup -S docker && \ addgroup dockstatapi docker -RUN apk add --no-cache bash curl && \ - adduser -h /api -s /bin/bash -D dockstatapi - HEALTHCHECK --interval=5m --timeout=3s \ CMD curl -f http://localhost:9876/api/status || exit 1 @@ -45,7 +41,8 @@ COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets -RUN npm install +RUN npm install --omit=dev && \ + rm -rf package-lock.json node_modules/.cache COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh @@ -59,4 +56,4 @@ RUN mkdir -p /api/src/data && \ STOPSIGNAL 130 USER dockstatapi -ENTRYPOINT [ "bash", "./entrypoint.sh", "--dev" ] +ENTRYPOINT [ "sh", "./entrypoint.sh", "--dev" ] From 955d41363e0d3057dd23b24f55f89fe49c2ce4cd Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 22:12:11 +0100 Subject: [PATCH 125/135] Fix: Use Node-20-slim in build stage --- docker/Dockerfile-base | 2 +- docker/Dockerfile-dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index 80b1ae8f..8dd89293 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:20-alpine AS builder +FROM node:20-slim AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index 7b439404..f3f3caea 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:20-alpine AS builder +FROM node:20-slim AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" From fdac5739dafcea9ac2f14cea8a5c29b5172699d5 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 22:53:43 +0100 Subject: [PATCH 126/135] Fix: Update to composeAction --- src/handlers/stack.ts | 48 ++++++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/src/handlers/stack.ts b/src/handlers/stack.ts index b3daa0f8..ad36373e 100644 --- a/src/handlers/stack.ts +++ b/src/handlers/stack.ts @@ -27,29 +27,35 @@ export async function validate(name: string): Promise { async function composeAction(option: string, name: string): Promise { const composeFile: string = path.join(PROJECT_ROOT, `stacks/${name}`); - switch (option) { - case "start": { - await compose.upAll({ cwd: composeFile, log: false }).then( - () => { - return true; - }, - (err: unknown) => { - throw new Error(err as string); - }, - ); - break; + try { + switch (option) { + case "start": { + await compose.upAll({ cwd: composeFile, log: false }); + break; + } + case "stop": { + await compose.downAll({ cwd: composeFile, log: false }); + break; + } + default: + throw new Error(`Invalid option: ${option}`); } - case "stop": { - await compose.downAll({ cwd: composeFile, log: false }).then( - () => { - return true; - }, - (err: unknown) => { - throw new Error(err as string); - }, - ); - break; + } catch (err) { + let errorMessage: string; + const portAllocated: string = "port is already allocated"; + + if (err instanceof Error) { + errorMessage = err.message; + } else if (typeof err === "object" && err !== null) { + errorMessage = JSON.stringify(err); + } else { + errorMessage = String(err); + } + + if (errorMessage.search(portAllocated)) { + errorMessage = "Port(s) already allocated"; } + throw new Error(errorMessage); } } From caa4b6ce6ae58a766fea42a1ea27d1f6ae5b6106 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sat, 15 Feb 2025 23:51:45 +0100 Subject: [PATCH 127/135] Fix: Make errors more verbose for debugging purpose --- src/handlers/stack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/stack.ts b/src/handlers/stack.ts index ad36373e..0f15e166 100644 --- a/src/handlers/stack.ts +++ b/src/handlers/stack.ts @@ -53,7 +53,7 @@ async function composeAction(option: string, name: string): Promise { } if (errorMessage.search(portAllocated)) { - errorMessage = "Port(s) already allocated"; + logger.error("Port(s) already allocated"); } throw new Error(errorMessage); } From fbc63f4e06124facc2ece85507c80ead12da1269 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 00:23:08 +0100 Subject: [PATCH 128/135] Fix: Add correct docker packages to Container --- docker/Dockerfile-base | 2 +- docker/Dockerfile-dev | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index 8dd89293..296dfe17 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -28,7 +28,7 @@ FROM node:20-alpine AS production WORKDIR /api -RUN apk add --no-cache docker-cli bash curl && \ +RUN apk add --no-cache docker docker-compose bash curl && \ adduser -h /api -s /bin/sh -D dockstatapi && \ addgroup -S docker && \ addgroup dockstatapi docker diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index f3f3caea..d0bd6236 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -28,7 +28,7 @@ FROM node:20-alpine AS production WORKDIR /api -RUN apk add --no-cache docker-cli bash curl && \ +RUN apk add --no-cache docker docker-compose bash curl && \ adduser -h /api -s /bin/sh -D dockstatapi && \ addgroup -S docker && \ addgroup dockstatapi docker From 0dc912eb9b35dc72531b7039455fdab03f816fbc Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 00:40:44 +0100 Subject: [PATCH 129/135] Fix: Adjust dockerfile (gosh i hope it works now) --- docker/Dockerfile-base | 62 ++++++++++++++++++++++++++---------------- docker/Dockerfile-dev | 62 ++++++++++++++++++++++++++---------------- 2 files changed, 78 insertions(+), 46 deletions(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index 296dfe17..bfee3a3d 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:20-slim AS builder +FROM node:20-alpine AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" @@ -15,45 +15,61 @@ WORKDIR /app ENV NODE_NO_WARNINGS=1 +RUN apk add --no-cache curl bash + COPY package*.json tsconfig.json environment.d.ts ./ -RUN npm install --production=false +RUN npm ci --include=dev COPY ./src ./src RUN mv ./src/sample-variable.json ./src/data/variables.json -RUN npm run build:mini -# Stage 2: Production stage -FROM node:20-alpine AS production +RUN npm run build:mini +# -------------------------------------- +# Stage 2: Dependency pruning stage +FROM node:20-alpine AS deps WORKDIR /api +COPY --from=builder /app/package*.json . +RUN npm ci --omit=dev -RUN apk add --no-cache docker docker-compose bash curl && \ - adduser -h /api -s /bin/sh -D dockstatapi && \ - addgroup -S docker && \ - addgroup dockstatapi docker +# -------------------------------------- +# Stage 3: Final production image +FROM node:20-alpine AS prod -HEALTHCHECK --interval=5m --timeout=3s \ - CMD curl -f http://localhost:9876/api/status || exit 1 +WORKDIR /api -COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src -COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets +RUN apk add --no-cache docker-cli bash curl && \ + mkdir -p /usr/libexec/docker/cli-plugins && \ + curl -sSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" \ + -o /usr/libexec/docker/cli-plugins/docker-compose && \ + chmod +x /usr/libexec/docker/cli-plugins/docker-compose && \ + rm -rf /var/cache/apk/* -RUN npm install --omit=dev && \ - rm -rf package-lock.json node_modules/.cache +ARG USER_ID=10001 +ARG GROUP_ID=10001 +RUN addgroup -g $GROUP_ID dockstatapi && \ + adduser -u $USER_ID -G dockstatapi -h /api -s /bin/sh -D dockstatapi -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh -RUN chmod +x /api/*.sh +COPY --from=builder --chown=dockstatapi:dockstatapi /app/dist/src ./src +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/config/swagger.yaml ./src/config/swagger.yaml +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/utils/assets ./src/utils/assets +COPY --from=deps --chown=dockstatapi:dockstatapi /api/node_modules ./node_modules -EXPOSE 9876 +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/entrypoint.sh . +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/createEnvFile.sh . +RUN chmod +x *.sh RUN mkdir -p /api/src/data && \ - chmod -R 777 /api/src/data /api && \ - chown -R dockstatapi:dockstatapi /api + chown -R dockstatapi:dockstatapi /api && \ + chmod -R 755 /api && \ + chmod 775 /api/src/data + +HEALTHCHECK --interval=5m --timeout=3s \ + CMD curl -f http://localhost:9876/api/status || exit 1 +EXPOSE 9876 STOPSIGNAL 130 USER dockstatapi + ENTRYPOINT [ "sh", "./entrypoint.sh", "--prod" ] diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index d0bd6236..dfda5354 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -1,5 +1,5 @@ # Stage 1: Build stage -FROM node:20-slim AS builder +FROM node:20-alpine AS builder LABEL maintainer="https://github.com/its4nik" LABEL version="2.0.1" @@ -15,45 +15,61 @@ WORKDIR /app ENV NODE_NO_WARNINGS=1 +RUN apk add --no-cache curl bash + COPY package*.json tsconfig.json environment.d.ts ./ -RUN npm install --production=false +RUN npm ci --include=dev COPY ./src ./src RUN mv ./src/sample-variable.json ./src/data/variables.json -RUN npm run build -# Stage 2: Production stage -FROM node:20-alpine AS production +RUN npm run build +# -------------------------------------- +# Stage 2: Dependency pruning stage +FROM node:20-alpine AS deps WORKDIR /api +COPY --from=builder /app/package*.json . +RUN npm ci --omit=dev -RUN apk add --no-cache docker docker-compose bash curl && \ - adduser -h /api -s /bin/sh -D dockstatapi && \ - addgroup -S docker && \ - addgroup dockstatapi docker +# -------------------------------------- +# Stage 3: Final production image +FROM node:20-alpine AS prod -HEALTHCHECK --interval=5m --timeout=3s \ - CMD curl -f http://localhost:9876/api/status || exit 1 +WORKDIR /api -COPY --chown=dockstatapi:dockstatapi --from=builder /app/dist/src /api/src -COPY --chown=dockstatapi:dockstatapi --from=builder /app/package*.json /api/ -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/config/swagger.yaml /api/src/config/swagger.yaml -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/utils/assets /api/src/utils/assets +RUN apk add --no-cache docker-cli bash curl && \ + mkdir -p /usr/libexec/docker/cli-plugins && \ + curl -sSL "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-$(uname -m)" \ + -o /usr/libexec/docker/cli-plugins/docker-compose && \ + chmod +x /usr/libexec/docker/cli-plugins/docker-compose && \ + rm -rf /var/cache/apk/* -RUN npm install --omit=dev && \ - rm -rf package-lock.json node_modules/.cache +ARG USER_ID=10001 +ARG GROUP_ID=10001 +RUN addgroup -g $GROUP_ID dockstatapi && \ + adduser -u $USER_ID -G dockstatapi -h /api -s /bin/sh -D dockstatapi -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/entrypoint.sh /api/entrypoint.sh -COPY --chown=dockstatapi:dockstatapi --from=builder /app/src/misc/createEnvFile.sh /api/createEnvFile.sh -RUN chmod +x /api/*.sh +COPY --from=builder --chown=dockstatapi:dockstatapi /app/dist/src ./src +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/config/swagger.yaml ./src/config/swagger.yaml +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/utils/assets ./src/utils/assets +COPY --from=deps --chown=dockstatapi:dockstatapi /api/node_modules ./node_modules -EXPOSE 9876 +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/entrypoint.sh . +COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/createEnvFile.sh . +RUN chmod +x *.sh RUN mkdir -p /api/src/data && \ - chmod -R 777 /api/src/data /api && \ - chown -R dockstatapi:dockstatapi /api + chown -R dockstatapi:dockstatapi /api && \ + chmod -R 755 /api && \ + chmod 775 /api/src/data + +HEALTHCHECK --interval=5m --timeout=3s \ + CMD curl -f http://localhost:9876/api/status || exit 1 +EXPOSE 9876 STOPSIGNAL 130 USER dockstatapi + ENTRYPOINT [ "sh", "./entrypoint.sh", "--dev" ] From c49da92f649c9e78ccd289929168f8aabe6300ff Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 15:46:33 +0100 Subject: [PATCH 130/135] Fix: Adjust entrypoints and dockerfiles --- docker/Dockerfile-base | 1 + docker/Dockerfile-dev | 1 + src/misc/entrypoint.sh | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile-base b/docker/Dockerfile-base index bfee3a3d..f21146ba 100644 --- a/docker/Dockerfile-base +++ b/docker/Dockerfile-base @@ -54,6 +54,7 @@ RUN addgroup -g $GROUP_ID dockstatapi && \ COPY --from=builder --chown=dockstatapi:dockstatapi /app/dist/src ./src COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/config/swagger.yaml ./src/config/swagger.yaml COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/utils/assets ./src/utils/assets +COPY --from=builder /app/package.json ./ COPY --from=deps --chown=dockstatapi:dockstatapi /api/node_modules ./node_modules COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/entrypoint.sh . diff --git a/docker/Dockerfile-dev b/docker/Dockerfile-dev index dfda5354..00b88008 100644 --- a/docker/Dockerfile-dev +++ b/docker/Dockerfile-dev @@ -54,6 +54,7 @@ RUN addgroup -g $GROUP_ID dockstatapi && \ COPY --from=builder --chown=dockstatapi:dockstatapi /app/dist/src ./src COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/config/swagger.yaml ./src/config/swagger.yaml COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/utils/assets ./src/utils/assets +COPY --from=builder /app/package.json ./ COPY --from=deps --chown=dockstatapi:dockstatapi /api/node_modules ./node_modules COPY --from=builder --chown=dockstatapi:dockstatapi /app/src/misc/entrypoint.sh . diff --git a/src/misc/entrypoint.sh b/src/misc/entrypoint.sh index 77b6236e..b352ca75 100755 --- a/src/misc/entrypoint.sh +++ b/src/misc/entrypoint.sh @@ -1,4 +1,3 @@ -# entrypoint.sh: #!/bin/bash VERSION="$(cat ./package.json | grep version | cut -d '"' -f 4)" From 9576cdc972022b2968012dc298c82b7635325582 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 15:51:54 +0100 Subject: [PATCH 131/135] Feat: Test new workflow --- .github/workflows/validation.yaml | 238 ++++++++---------------------- 1 file changed, 63 insertions(+), 175 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index 52797fcc..dfa18edf 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -1,20 +1,18 @@ -name: "Run all tests" +name: "CI/CD Pipeline" on: push: release: - types: - - published + types: [published] jobs: validation: + name: "Code Validation & Tests" runs-on: ubuntu-24.04 - name: "Validation" permissions: - security-events: write - packages: read actions: read contents: read + packages: read steps: - name: Checkout uses: actions/checkout@v4 @@ -31,77 +29,37 @@ jobs: - name: Create varaibles.json run: npm run local-env-file - - name: Run prettier + - name: Run code formatting run: npm run prettier - name: Run linter run: npm run lint - - name: Build + - name: Build project run: npm run build:mini - name: Audit packages run: npm audit --audit-level=high - - name: Jests + - name: Run tests run: npm run test:silent - ToDo: - needs: validation - runs-on: ubuntu-20.04 - name: "ToDo comment to issue" - permissions: - contents: write - issues: write - pull-requests: write - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: "TODO to Issue" - uses: "alstr/todo-to-issue-action@v5" - with: - INSERT_ISSUE_URLS: "true" - - - name: Set Git user - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Commit and Push Changes - run: | - git add -A - if [[ `git status --porcelain` ]]; then - git commit -m "Automatically added GitHub issue links to TODOs" - git push - else - echo "No changes to commit" - fi - - CodeQL: - needs: [ToDo] + security-analysis: + name: "Security Analysis" runs-on: ubuntu-24.04 - name: "Analyze TypeScript" + needs: validation permissions: security-events: write - packages: read - actions: read contents: read - + packages: read steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: javascript-typescript - build-mode: none queries: security-extended config: | query-filter: @@ -110,194 +68,124 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 - with: - category: "/language:javascript-typescript" - Anchore: - needs: [ToDo] + container-scanning: + name: "Container Security" runs-on: ubuntu-24.04 - name: "Anchore" + needs: validation permissions: security-events: write - packages: read - actions: read contents: read steps: - - name: Set up Grype installation path - run: echo "$HOME/bin" >> $GITHUB_PATH - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: "3.13" + - name: Checkout repository + uses: actions/checkout@v4 - name: Download Grype run: | curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b $HOME/bin + echo "$HOME/bin" >> $GITHUB_PATH - - uses: actions/checkout@v4 - - - name: Build the Container image + - name: Build Docker image run: docker build . --file docker/Dockerfile-base --tag localbuild/testimage:latest - - name: Run Grype test + - name: Run vulnerability scan run: grype -o sarif localbuild/testimage:latest > results.sarif - - name: Upload Anchore scan SARIF report + - name: Upload SARIF report uses: github/codeql-action/upload-sarif@v3 with: sarif_file: ./results.sarif - test-building: - needs: [ToDo] + build-test: + name: "Docker Build Test" runs-on: ubuntu-24.04 - name: "Test building" + needs: validation permissions: - security-events: write + contents: read packages: read - actions: read - contents: write steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Generate Docker tags - uses: docker/metadata-action@v5 - id: metadata - with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=${{ github.sha }} - - - name: Build and Push Docker Images + - name: Build Docker image uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile-base platforms: linux/amd64,linux/arm64 push: false - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max - build-dev: - name: "Dev-build" - permissions: - security-events: read - packages: write - actions: read - contents: read + todo-management: + name: "TODO Issue Management" runs-on: ubuntu-24.04 - if: github.ref_name == 'dev' - needs: [test-building, Anchore, CodeQL] + needs: validation + permissions: + contents: write + issues: write steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Setup python - uses: actions/setup-python@v5 - with: - python-version: "3.13" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Github Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ github.token }} + - name: Checkout repository + uses: actions/checkout@v4 - - name: Generate Docker tags - uses: docker/metadata-action@v5 - id: metadata + - name: Process TODOs + uses: alstr/todo-to-issue-action@v5 with: - images: ghcr.io/${{ github.repository }} - tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=nightly - flavor: | - latest=false + INSERT_ISSUE_URLS: "true" - - name: Build and Push Docker Images - uses: docker/build-push-action@v6 - with: - context: . - file: docker/Dockerfile-dev - platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max + - name: Commit changes + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git add -A + if [[ $(git status --porcelain) ]]; then + git commit -m "Automatically process TODOs [skip ci]" + git push + fi - build-pre-release: - name: "Pre-Release-build" + deployment: + name: "Docker Deployment" + runs-on: ubuntu-24.04 + needs: [security-analysis, container-scanning, build-test] permissions: - security-events: read packages: write - actions: read contents: read - runs-on: ubuntu-24.04 - if: "github.event.release.prerelease" - needs: [test-building, Anchore, CodeQL] + strategy: + matrix: + type: [dev, pre-release] steps: - - name: Checkout Repository - uses: actions/checkout@v3 - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + - name: Checkout repository + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Github Container Registry + - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} - password: ${{ github.token }} + password: ${{ secrets.GITHUB_TOKEN }} - - name: Generate Docker tags + - name: Determine tags + id: tags uses: docker/metadata-action@v5 - id: metadata with: images: ghcr.io/${{ github.repository }} tags: | - type=raw,enable=true,priority=200,prefix=,suffix=,value=pre - flavor: | - latest=false + type=raw,enable=${{ matrix.type == 'dev' && github.ref_name == 'dev' || matrix.type == 'pre-release' && github.event.release.prerelease }},value=${{ matrix.type == 'dev' && 'nightly' || 'pre' }} - - name: Build and Push Docker Images + - name: Build and push uses: docker/build-push-action@v6 with: context: . file: docker/Dockerfile-dev platforms: linux/amd64,linux/arm64 - push: true - tags: ${{ steps.metadata.outputs.tags }} - labels: ${{ steps.metadata.outputs.labels }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.tags.outputs.tags }} + labels: ${{ steps.tags.outputs.labels }} cache-from: type=gha cache-to: type=gha,mode=max From 749c28653a34d8ab8dabdb2670bbc0f9f0b8e241 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 16:02:57 +0100 Subject: [PATCH 132/135] Feat: Test new workflow --- .github/workflows/validation.yaml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index dfa18edf..d4349e9f 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -155,7 +155,11 @@ jobs: contents: read strategy: matrix: - type: [dev, pre-release] + type: [dev, pre-release, release] + if: > + (matrix.type == 'dev' && github.ref_name == 'dev') + || (matrix.type == 'pre-release' && github.event_name == 'release' && github.event.release.prerelease) + || (matrix.type == 'release' && github.event_name == 'release' && !github.event.release.prerelease) steps: - name: Checkout repository uses: actions/checkout@v4 @@ -176,7 +180,7 @@ jobs: with: images: ghcr.io/${{ github.repository }} tags: | - type=raw,enable=${{ matrix.type == 'dev' && github.ref_name == 'dev' || matrix.type == 'pre-release' && github.event.release.prerelease }},value=${{ matrix.type == 'dev' && 'nightly' || 'pre' }} + type=raw,enable=${{ matrix.type == 'dev' && github.ref_name == 'dev' || matrix.type == 'pre-release' && github.event_name == 'release' && github.event.release.prerelease || matrix.type == 'release' && github.event_name == 'release' && !github.event.release.prerelease }},value=${{ matrix.type == 'dev' && 'nightly' || matrix.type == 'pre-release' && 'pre' || matrix.type == 'release' && 'latest' }} - name: Build and push uses: docker/build-push-action@v6 From dec86d312c5c146d2c3dbba46dae41de7ecc54a3 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Sun, 16 Feb 2025 16:06:19 +0100 Subject: [PATCH 133/135] Fix: test new workflow --- .github/workflows/validation.yaml | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validation.yaml b/.github/workflows/validation.yaml index d4349e9f..9a9ec937 100644 --- a/.github/workflows/validation.yaml +++ b/.github/workflows/validation.yaml @@ -155,19 +155,33 @@ jobs: contents: read strategy: matrix: - type: [dev, pre-release, release] - if: > - (matrix.type == 'dev' && github.ref_name == 'dev') - || (matrix.type == 'pre-release' && github.event_name == 'release' && github.event.release.prerelease) - || (matrix.type == 'release' && github.event_name == 'release' && !github.event.release.prerelease) + include: + - type: dev + # Only enable when pushing to the dev branch + enabled: ${{ github.ref_name == 'dev' }} + - type: pre-release + # Only enable when a release event is published and it's a prerelease + enabled: ${{ github.event_name == 'release' && github.event.release.prerelease }} + - type: release + # Only enable when a release event is published and it's NOT a prerelease + enabled: ${{ github.event_name == 'release' && !github.event.release.prerelease }} steps: + - name: Exit early if deployment is not enabled + if: ${{ !matrix.enabled }} + run: | + echo "Skipping deployment for matrix type '${{ matrix.type }}' because conditions are not met." + exit 0 + - name: Checkout repository + if: ${{ matrix.enabled }} uses: actions/checkout@v4 - name: Set up Docker Buildx + if: ${{ matrix.enabled }} uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry + if: ${{ matrix.enabled }} uses: docker/login-action@v3 with: registry: ghcr.io @@ -175,14 +189,16 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Determine tags + if: ${{ matrix.enabled }} id: tags uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | - type=raw,enable=${{ matrix.type == 'dev' && github.ref_name == 'dev' || matrix.type == 'pre-release' && github.event_name == 'release' && github.event.release.prerelease || matrix.type == 'release' && github.event_name == 'release' && !github.event.release.prerelease }},value=${{ matrix.type == 'dev' && 'nightly' || matrix.type == 'pre-release' && 'pre' || matrix.type == 'release' && 'latest' }} + type=raw,value=${{ matrix.type == 'dev' && 'nightly' || matrix.type == 'pre-release' && 'pre' || matrix.type == 'release' && 'latest' }} - name: Build and push + if: ${{ matrix.enabled }} uses: docker/build-push-action@v6 with: context: . From d1566b16f3e76e7494c438e92c427efc9f0d4494 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 11 Mar 2025 18:38:04 +0100 Subject: [PATCH 134/135] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 25778667..f1be46f2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,7 @@ +# Deprecation Warning! +# V2 is abondend, since there whhere to many issues in the codebase! +# Please see v3 (next commit from this one onwards, all other branches which are not based on bun will be deleted!) + # DockStatAPI v2 ![Dockstat Logo](.github/DockStat.png) From 0ed8ebdfc157d342d351e91bcfc9d2403fe49481 Mon Sep 17 00:00:00 2001 From: ItsNik Date: Tue, 11 Mar 2025 18:38:26 +0100 Subject: [PATCH 135/135] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1be46f2..4fc979cf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Deprecation Warning! -# V2 is abondend, since there whhere to many issues in the codebase! +# V2 is abondend, since there where to many issues in the codebase! # Please see v3 (next commit from this one onwards, all other branches which are not based on bun will be deleted!) # DockStatAPI v2