diff --git a/.github/workflows/jhipsterCiCd.yml b/.github/workflows/jhipsterCiCd.yml new file mode 100644 index 0000000..d473ee4 --- /dev/null +++ b/.github/workflows/jhipsterCiCd.yml @@ -0,0 +1,45 @@ +name: JHipster CI/CD Pipeline +on: [push, pull_request] +jobs: + pipeline: + name: app pipeline + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]') && !contains(github.event.head_commit.message, '[skip ci]') && !contains(github.event.pull_request.title, '[skip ci]') && !contains(github.event.pull_request.title, '[ci skip]')" + timeout-minutes: 40 + env: + NODE_VERSION: 18.18.2 + SPRING_OUTPUT_ANSI_ENABLED: DETECT + SPRING_JPA_SHOW_SQL: false + JHI_DISABLE_WEBPACK_LOGS: true + NG_CLI_ANALYTICS: false + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 + with: + node-version: 18.18.2 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17 + - name: Install node.js packages + run: npm install + working-directory: ./2_supplychain/2_praxis/DemoContainerSecurity/Demo/app + - name: Run backend test + run: | + chmod +x mvnw + npm run ci:backend:test + working-directory: ./2_supplychain/2_praxis/DemoContainerSecurity/Demo/app + - name: Run frontend test + run: npm run ci:frontend:test + working-directory: ./2_supplychain/2_praxis/DemoContainerSecurity/Demo/app + - name: Package application + run: npm run java:jar:prod + working-directory: ./2_supplychain/2_praxis/DemoContainerSecurity/Demo/app + - name: Build and publish docker image + if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')) + run: | + GIT_TAG=:${GITHUB_REF#refs/tags/} + DOCKER_TAG=${GIT_TAG#:refs/heads/main} + ./mvnw -ntp jib:build -Djib.to.image=jhipster/undefined${DOCKER_TAG} -Djib.to.auth.username="${{ secrets.DOCKER_USERNAME }}" -Djib.to.auth.password="${{ secrets.DOCKER_PASSWORD }}" -Pprod + working-directory: ./2_supplychain/2_praxis/DemoContainerSecurity/Demo/app + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/Dockerfile b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/Dockerfile new file mode 100644 index 0000000..a914fd2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/Dockerfile @@ -0,0 +1,25 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java/.devcontainer/base.Dockerfile + +# [Choice] Java version (use -bullseye variants on local arm64/Apple Silicon): 17, 17-bullseye, 17-buster +ARG VARIANT="17" +FROM mcr.microsoft.com/vscode/devcontainers/java:0-${VARIANT} + +# [Option] Install Maven +ARG INSTALL_MAVEN="false" +ARG MAVEN_VERSION="" +# [Option] Install Gradle +ARG INSTALL_GRADLE="false" +ARG GRADLE_VERSION="" +RUN if [ "${INSTALL_MAVEN}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install maven \"${MAVEN_VERSION}\""; fi \ + && if [ "${INSTALL_GRADLE}" = "true" ]; then su vscode -c "umask 0002 && . /usr/local/sdkman/bin/sdkman-init.sh && sdk install gradle \"${GRADLE_VERSION}\""; fi + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/devcontainer.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f662e1a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.devcontainer/devcontainer.json @@ -0,0 +1,53 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/java +{ + "name": "App", + "build": { + "dockerfile": "Dockerfile", + "args": { + // Update the VARIANT arg to pick a Java version: 17, 19 + // Append -bullseye or -buster to pin to an OS version. + // Use the -bullseye variants on local arm64/Apple Silicon. + "VARIANT": "17-bullseye", + // Options + // maven and gradle wrappers are used by default, we don't need them installed globally + // "INSTALL_MAVEN": "true", + // "INSTALL_GRADLE": "false", + "NODE_VERSION": "18.18.2" + } + }, + + "customizations": { + "vscode": { + // Set *default* container specific settings.json values on container create. + "settings": { + "java.jdt.ls.java.home": "/docker-java-home" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "angular.ng-template", + "christian-kohler.npm-intellisense", + "firsttris.vscode-jest-runner", + "ms-vscode.vscode-typescript-tslint-plugin", + "dbaeumer.vscode-eslint", + "vscjava.vscode-java-pack", + "pivotal.vscode-boot-dev-pack", + "esbenp.prettier-vscode" + ] + } + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + "forwardPorts": [4200, 3001, 9000, 8080], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "java -version", + + // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "docker-in-docker": "latest", + "docker-from-docker": "latest" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.editorconfig b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.editorconfig new file mode 100644 index 0000000..c2fa6a2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.editorconfig @@ -0,0 +1,23 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +# We recommend you to keep these unchanged +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Change these settings to your own preference +indent_style = space +indent_size = 4 + +[*.{ts,tsx,js,jsx,json,css,scss,yml,html,vue}] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintignore b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintignore new file mode 100644 index 0000000..4997c23 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintignore @@ -0,0 +1,10 @@ +node_modules/ +src/main/docker/ +jest.conf.js +webpack/ +target/ +build/ +node/ +coverage/ +postcss.config.js +target/classes/static/ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintrc.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintrc.json new file mode 100644 index 0000000..9f1260c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.eslintrc.json @@ -0,0 +1,99 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@angular-eslint/eslint-plugin", "@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@angular-eslint/recommended", + "prettier", + "eslint-config-prettier" + ], + "env": { + "browser": true, + "es6": true, + "commonjs": true + }, + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "project": ["./tsconfig.app.json", "./src/test/javascript/cypress/tsconfig.json", "./tsconfig.spec.json"] + }, + "rules": { + "@angular-eslint/component-selector": [ + "error", + { + "type": "element", + "prefix": "jhi", + "style": "kebab-case" + } + ], + "@angular-eslint/directive-selector": [ + "error", + { + "type": "attribute", + "prefix": "jhi", + "style": "camelCase" + } + ], + "@angular-eslint/relative-url-prefix": "error", + "@typescript-eslint/ban-types": [ + "error", + { + "extendDefaults": true, + "types": { + "{}": false + } + } + ], + "@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true }], + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/member-ordering": [ + "error", + { + "default": [ + "public-static-field", + "protected-static-field", + "private-static-field", + "public-instance-field", + "protected-instance-field", + "private-instance-field", + "constructor", + "public-static-method", + "protected-static-method", + "private-static-method", + "public-instance-method", + "protected-instance-method", + "private-instance-method" + ] + } + ], + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-shadow": ["error"], + "@typescript-eslint/no-unnecessary-condition": "error", + "@typescript-eslint/no-unsafe-argument": "off", + "@typescript-eslint/no-unsafe-assignment": "off", + "@typescript-eslint/no-unsafe-call": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/prefer-nullish-coalescing": "error", + "@typescript-eslint/prefer-optional-chain": "error", + "@typescript-eslint/unbound-method": "off", + "arrow-body-style": "error", + "curly": "error", + "eqeqeq": ["error", "always", { "null": "ignore" }], + "guard-for-in": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-console": ["error", { "allow": ["warn", "error"] }], + "no-eval": "error", + "no-labels": "error", + "no-new": "error", + "no-new-wrappers": "error", + "object-shorthand": ["error", "always", { "avoidExplicitReturnArrows": true }], + "radix": "error", + "spaced-comment": ["warn", "always"] + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitattributes b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitattributes new file mode 100644 index 0000000..ca61722 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitattributes @@ -0,0 +1,150 @@ +# This file is inspired by https://github.com/alexkaratarakis/gitattributes +# +# Auto detect text files and perform LF normalization +# http://davidlaing.com/2012/09/19/customise-your-gitattributes-to-become-a-git-ninja/ +* text=auto + +# The above will handle all files NOT found below +# These files are text and should be normalized (Convert crlf => lf) + +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf +*.coffee text +*.css text +*.cql text +*.df text +*.ejs text +*.html text +*.java text +*.js text +*.json text +*.less text +*.properties text +*.sass text +*.scss text +*.sh text eol=lf +*.sql text +*.txt text +*.ts text +*.xml text +*.yaml text +*.yml text + +# Documents +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain +*.markdown text +*.md text +*.adoc text +*.textile text +*.mustache text +*.csv text +*.tab text +*.tsv text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +# SVG treated as an asset (binary) by default. If you want to treat it as text, +# comment-out the following line and uncomment the line after. +*.svg binary +#*.svg text +*.eps binary + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.jar binary +*.war binary + +## LINTERS +.csslintrc text +.eslintrc text +.jscsrc text +.jshintrc text +.jshintignore text +.stylelintrc text + +## CONFIGS +*.conf text +*.config text +.editorconfig text +.gitattributes text +.gitconfig text +.gitignore text +.htaccess text +*.npmignore text + +## HEROKU +Procfile text +.slugignore text + +## AUDIO +*.kar binary +*.m4a binary +*.mid binary +*.midi binary +*.mp3 binary +*.ogg binary +*.ra binary + +## VIDEO +*.3gpp binary +*.3gp binary +*.as binary +*.asf binary +*.asx binary +*.fla binary +*.flv binary +*.m4v binary +*.mng binary +*.mov binary +*.mp4 binary +*.mpeg binary +*.mpg binary +*.swc binary +*.swf binary +*.webm binary + +## ARCHIVES +*.7z binary +*.gz binary +*.rar binary +*.tar binary +*.zip binary + +## FONTS +*.ttf binary +*.eot binary +*.otf binary +*.woff binary +*.woff2 binary diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitignore b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitignore new file mode 100644 index 0000000..d543a07 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.gitignore @@ -0,0 +1,151 @@ +###################### +# Node +###################### +/node/ +node_tmp/ +node_modules/ +npm-debug.log.* +/.awcache/* +/.cache-loader/* + +###################### +# SASS +###################### +.sass-cache/ + +###################### +# Eclipse +###################### +*.pydevproject +.project +.metadata +tmp/ +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath +.factorypath + +# External tool builders +.externalToolBuilders/** + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + +# STS-specific +/.sts4-cache/* + +###################### +# IntelliJ +###################### +.idea/ +*.iml +*.iws +*.ipr +*.ids +*.orig +classes/ +out/ + +###################### +# Visual Studio Code +###################### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +###################### +# Maven +###################### +/log/ +/target/ + +###################### +# Gradle +###################### +.gradle/ +/build/ + +###################### +# Package Files +###################### +*.jar +*.war +*.ear +*.db + +###################### +# Windows +###################### +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + +###################### +# Mac OSX +###################### +.DS_Store +.svn + +# Thumbnails +._* + +# Files that might appear on external disk +.Spotlight-V100 +.Trashes + +###################### +# Directories +###################### +/bin/ +/deploy/ + +###################### +# Logs +###################### +*.log* + +###################### +# Others +###################### +*.class +*.*~ +*~ +.merge_file* + +###################### +# Gradle Wrapper +###################### +!gradle/wrapper/gradle-wrapper.jar + +###################### +# Maven Wrapper +###################### +!.mvn/wrapper/maven-wrapper.jar + +###################### +# ESLint +###################### +.eslintcache + +###################### +# Code coverage +###################### +/coverage/ +/.nyc_output/ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/jvm.config b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/jvm.config new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/jvm.config @@ -0,0 +1 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.jar b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000..cb28b0e Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.jar differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..eacdc9e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierignore b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierignore new file mode 100644 index 0000000..fec4ac0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierignore @@ -0,0 +1,11 @@ +node_modules +package-lock.json +.git +target/ + +# Generated by jhipster:client +target/classes/static/ + +# Generated by jhipster:maven +target +.mvn diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierrc b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierrc new file mode 100644 index 0000000..003db80 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.prettierrc @@ -0,0 +1,22 @@ +# Prettier configuration + +printWidth: 140 +singleQuote: true +tabWidth: 2 +useTabs: false + +# js and ts rules: +arrowParens: avoid + +# jsx and tsx rules: +bracketSameLine: false + +plugins: + - prettier-plugin-packagejson + - prettier-plugin-java + +# java rules: +overrides: + - files: "*.java" + options: + tabWidth: 4 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.yo-rc.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.yo-rc.json new file mode 100644 index 0000000..5988e7b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/.yo-rc.json @@ -0,0 +1,43 @@ +{ + "generator-jhipster": { + "applicationType": "monolith", + "authenticationType": "jwt", + "baseName": "app", + "buildTool": "maven", + "cacheProvider": "ehcache", + "clientFramework": "angular", + "clientTestFrameworks": ["cypress"], + "clientTheme": "none", + "creationTimestamp": 1702145565505, + "cypressAudit": false, + "cypressCoverage": false, + "databaseType": "sql", + "devDatabaseType": "mysql", + "devServerPort": 4200, + "enableGradleEnterprise": null, + "enableHibernateCache": true, + "enableSwaggerCodegen": false, + "enableTranslation": true, + "entities": [], + "gradleEnterpriseHost": null, + "jhipsterVersion": "8.0.0", + "jwtSecretKey": "MmQ3MzkwYzgzMzMxOTYwN2IxMGViYTM4MjlmNThkN2JhMzNkMDQ5ZjIyNzJkNDhlNWNkZGNhZDdjYzRiNzIyMjVlYTAwMmI5NTA3ZmIxZTQzODNhZWY5OWFjZTBiNTVmODlhMzY4NDhjMmQ1Y2UzNGMwMTEzNWFiNWU5YWEwMTY=", + "languages": ["en", "fr", "de"], + "messageBroker": false, + "microfrontend": null, + "microfrontends": [], + "monorepository": true, + "nativeLanguage": "en", + "packageName": "de.novatec.ittage.app", + "prodDatabaseType": "mysql", + "reactive": false, + "searchEngine": false, + "serverPort": null, + "serverSideOptions": [], + "serviceDiscoveryType": false, + "skipCommitHook": true, + "testFrameworks": ["gatling", "cypress"], + "websocket": false, + "withAdminUi": true + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/README.md b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/README.md new file mode 100644 index 0000000..05a14eb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/README.md @@ -0,0 +1,281 @@ +# app + +This application was generated using JHipster 8.0.0, you can find documentation and help at [https://www.jhipster.tech/documentation-archive/v8.0.0](https://www.jhipster.tech/documentation-archive/v8.0.0). + +## Project Structure + +Node is required for generation and recommended for development. `package.json` is always generated for a better development experience with prettier, commit hooks, scripts and so on. + +In the project root, JHipster generates configuration files for tools like git, prettier, eslint, husky, and others that are well known and you can find references in the web. + +`/src/*` structure follows default Java structure. + +- `.yo-rc.json` - Yeoman configuration file + JHipster configuration is stored in this file at `generator-jhipster` key. You may find `generator-jhipster-*` for specific blueprints configuration. +- `.yo-resolve` (optional) - Yeoman conflict resolver + Allows to use a specific action when conflicts are found skipping prompts for files that matches a pattern. Each line should match `[pattern] [action]` with pattern been a [Minimatch](https://github.com/isaacs/minimatch#minimatch) pattern and action been one of skip (default if ommited) or force. Lines starting with `#` are considered comments and are ignored. +- `.jhipster/*.json` - JHipster entity configuration files + +- `npmw` - wrapper to use locally installed npm. + JHipster installs Node and npm locally using the build tool by default. This wrapper makes sure npm is installed locally and uses it avoiding some differences different versions can cause. By using `./npmw` instead of the traditional `npm` you can configure a Node-less environment to develop or test your application. +- `/src/main/docker` - Docker configurations for the application and services that the application depends on + +## Development + +Before you can build this project, you must install and configure the following dependencies on your machine: + +1. [Node.js][]: We use Node to run a development web server and build the project. + Depending on your system, you can install Node either from source or as a pre-packaged bundle. + +After installing Node, you should be able to run the following command to install development tools. +You will only need to run this command when dependencies change in [package.json](package.json). + +``` +npm install +``` + +We use npm scripts and [Angular CLI][] with [Webpack][] as our build system. + +Run the following commands in two separate terminals to create a blissful development experience where your browser +auto-refreshes when files change on your hard drive. + +``` +./mvnw +npm start +``` + +Npm is also used to manage CSS and JavaScript dependencies used in this application. You can upgrade dependencies by +specifying a newer version in [package.json](package.json). You can also run `npm update` and `npm install` to manage dependencies. +Add the `help` flag on any command to see how you can use it. For example, `npm help update`. + +The `npm run` command will list all of the scripts available to run for this project. + +### PWA Support + +JHipster ships with PWA (Progressive Web App) support, and it's turned off by default. One of the main components of a PWA is a service worker. + +The service worker initialization code is disabled by default. To enable it, uncomment the following code in `src/main/webapp/app/app.module.ts`: + +```typescript +ServiceWorkerModule.register('ngsw-worker.js', { enabled: false }), +``` + +### Managing dependencies + +For example, to add [Leaflet][] library as a runtime dependency of your application, you would run following command: + +``` +npm install --save --save-exact leaflet +``` + +To benefit from TypeScript type definitions from [DefinitelyTyped][] repository in development, you would run following command: + +``` +npm install --save-dev --save-exact @types/leaflet +``` + +Then you would import the JS and CSS files specified in library's installation instructions so that [Webpack][] knows about them: +Edit [src/main/webapp/app/app.module.ts](src/main/webapp/app/app.module.ts) file: + +``` +import 'leaflet/dist/leaflet.js'; +``` + +Edit [src/main/webapp/content/scss/vendor.scss](src/main/webapp/content/scss/vendor.scss) file: + +``` +@import 'leaflet/dist/leaflet.css'; +``` + +Note: There are still a few other things remaining to do for Leaflet that we won't detail here. + +For further instructions on how to develop with JHipster, have a look at [Using JHipster in development][]. + +### Using Angular CLI + +You can also use [Angular CLI][] to generate some custom client code. + +For example, the following command: + +``` +ng generate component my-component +``` + +will generate few files: + +``` +create src/main/webapp/app/my-component/my-component.component.html +create src/main/webapp/app/my-component/my-component.component.ts +update src/main/webapp/app/app.module.ts +``` + +## Building for production + +### Packaging as jar + +To build the final jar and optimize the app application for production, run: + +``` +./mvnw -Pprod clean verify +``` + +This will concatenate and minify the client CSS and JavaScript files. It will also modify `index.html` so it references these new files. +To ensure everything worked, run: + +``` +java -jar target/*.jar +``` + +Then navigate to [http://localhost:8080](http://localhost:8080) in your browser. + +Refer to [Using JHipster in production][] for more details. + +### Packaging as war + +To package your application as a war in order to deploy it to an application server, run: + +``` +./mvnw -Pprod,war clean verify +``` + +### JHipster Control Center + +JHipster Control Center can help you manage and control your application(s). You can start a local control center server (accessible on http://localhost:7419) with: + +``` +docker compose -f src/main/docker/jhipster-control-center.yml up +``` + +## Testing + +### Spring Boot tests + +To launch your application's tests, run: + +``` +./mvnw verify +``` + +### Client tests + +Unit tests are run by [Jest][]. They're located in [src/test/javascript/](src/test/javascript/) and can be run with: + +``` +npm test +``` + +UI end-to-end tests are powered by [Cypress][]. They're located in [src/test/javascript/cypress](src/test/javascript/cypress) +and can be run by starting Spring Boot in one terminal (`./mvnw spring-boot:run`) and running the tests (`npm run e2e`) in a second one. + +#### Lighthouse audits + +You can execute automated [lighthouse audits][https://developers.google.com/web/tools/lighthouse/] with [cypress audits][https://github.com/mfrachet/cypress-audit] by running `npm run e2e:cypress:audits`. +You should only run the audits when your application is packaged with the production profile. +The lighthouse report is created in `target/cypress/lhreport.html` + +### Gatling + +Performance tests are run by [Gatling][] and written in Scala. They're located in [src/test/java/gatling/simulations](src/test/java/gatling/simulations). + +You can execute all Gatling tests with + +``` +./mvnw gatling:test +``` + +## Others + +### Code quality using Sonar + +Sonar is used to analyse code quality. You can start a local Sonar server (accessible on http://localhost:9001) with: + +``` +docker compose -f src/main/docker/sonar.yml up -d +``` + +Note: we have turned off forced authentication redirect for UI in [src/main/docker/sonar.yml](src/main/docker/sonar.yml) for out of the box experience while trying out SonarQube, for real use cases turn it back on. + +You can run a Sonar analysis with using the [sonar-scanner](https://docs.sonarqube.org/display/SCAN/Analyzing+with+SonarQube+Scanner) or by using the maven plugin. + +Then, run a Sonar analysis: + +``` +./mvnw -Pprod clean verify sonar:sonar -Dsonar.login=admin -Dsonar.password=admin +``` + +If you need to re-run the Sonar phase, please be sure to specify at least the `initialize` phase since Sonar properties are loaded from the sonar-project.properties file. + +``` +./mvnw initialize sonar:sonar -Dsonar.login=admin -Dsonar.password=admin +``` + +Additionally, Instead of passing `sonar.password` and `sonar.login` as CLI arguments, these parameters can be configured from [sonar-project.properties](sonar-project.properties) as shown below: + +``` +sonar.login=admin +sonar.password=admin +``` + +For more information, refer to the [Code quality page][]. + +### Using Docker to simplify development (optional) + +You can use Docker to improve your JHipster development experience. A number of docker-compose configuration are available in the [src/main/docker](src/main/docker) folder to launch required third party services. + +For example, to start a mysql database in a docker container, run: + +``` +docker compose -f src/main/docker/mysql.yml up -d +``` + +To stop it and remove the container, run: + +``` +docker compose -f src/main/docker/mysql.yml down +``` + +You can also fully dockerize your application and all the services that it depends on. +To achieve this, first build a docker image of your app by running: + +``` +npm run java:docker +``` + +Or build a arm64 docker image when using an arm64 processor os like MacOS with M1 processor family running: + +``` +npm run java:docker:arm64 +``` + +Then run: + +``` +docker compose -f src/main/docker/app.yml up -d +``` + +When running Docker Desktop on MacOS Big Sur or later, consider enabling experimental `Use the new Virtualization framework` for better processing performance ([disk access performance is worse](https://github.com/docker/roadmap/issues/7)). + +For more information refer to [Using Docker and Docker-Compose][], this page also contains information on the docker-compose sub-generator (`jhipster docker-compose`), which is able to generate docker configurations for one or several JHipster applications. + +## Continuous Integration (optional) + +To configure CI for your project, run the ci-cd sub-generator (`jhipster ci-cd`), this will let you generate configuration files for a number of Continuous Integration systems. Consult the [Setting up Continuous Integration][] page for more information. + +[JHipster Homepage and latest documentation]: https://www.jhipster.tech +[JHipster 8.0.0 archive]: https://www.jhipster.tech/documentation-archive/v8.0.0 +[Using JHipster in development]: https://www.jhipster.tech/documentation-archive/v8.0.0/development/ +[Using Docker and Docker-Compose]: https://www.jhipster.tech/documentation-archive/v8.0.0/docker-compose +[Using JHipster in production]: https://www.jhipster.tech/documentation-archive/v8.0.0/production/ +[Running tests page]: https://www.jhipster.tech/documentation-archive/v8.0.0/running-tests/ +[Code quality page]: https://www.jhipster.tech/documentation-archive/v8.0.0/code-quality/ +[Setting up Continuous Integration]: https://www.jhipster.tech/documentation-archive/v8.0.0/setting-up-ci/ +[Node.js]: https://nodejs.org/ +[NPM]: https://www.npmjs.com/ +[Webpack]: https://webpack.github.io/ +[BrowserSync]: https://www.browsersync.io/ +[Jest]: https://facebook.github.io/jest/ +[Cypress]: https://www.cypress.io/ +[Leaflet]: https://leafletjs.com/ +[DefinitelyTyped]: https://definitelytyped.org/ +[Angular CLI]: https://cli.angular.io/ +[Gatling]: https://gatling.io/ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/angular.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/angular.json new file mode 100644 index 0000000..6f050e2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/angular.json @@ -0,0 +1,110 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "app": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src/main/webapp", + "prefix": "jhi", + "architect": { + "build": { + "builder": "@angular-builders/custom-webpack:browser", + "options": { + "customWebpackConfig": { + "path": "./webpack/webpack.custom.js" + }, + "outputPath": "target/classes/static/", + "index": "src/main/webapp/index.html", + "main": "src/main/webapp/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/main/webapp/content", + "src/main/webapp/favicon.ico", + "src/main/webapp/manifest.webapp", + "src/main/webapp/robots.txt" + ], + "styles": ["src/main/webapp/content/scss/vendor.scss", "src/main/webapp/content/scss/global.scss"], + "scripts": [] + }, + "configurations": { + "production": { + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "serviceWorker": true, + "ngswConfigPath": "ngsw-config.json", + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ] + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-builders/custom-webpack:dev-server", + "options": { + "browserTarget": "app:build:development", + "port": 4200 + }, + "configurations": { + "production": { + "browserTarget": "app:build:production" + }, + "development": { + "browserTarget": "app:build:development" + } + }, + "defaultConfiguration": "development" + }, + "test": { + "builder": "@angular-builders/jest:run", + "options": { + "configPath": "jest.conf.js", + "tsConfig": "tsconfig.spec.json" + } + } + } + } + }, + "cli": { + "cache": { + "enabled": true, + "path": "./target/angular/", + "environment": "all" + }, + "packageManager": "npm" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/checkstyle.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/checkstyle.xml new file mode 100644 index 0000000..5d5ae65 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/checkstyle.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/cypress.config.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/cypress.config.ts new file mode 100644 index 0000000..862010b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/cypress.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from 'cypress'; + +export default defineConfig({ + video: false, + fixturesFolder: 'src/test/javascript/cypress/fixtures', + screenshotsFolder: 'target/cypress/screenshots', + downloadsFolder: 'target/cypress/downloads', + videosFolder: 'target/cypress/videos', + chromeWebSecurity: true, + viewportWidth: 1200, + viewportHeight: 720, + retries: 2, + env: { + authenticationUrl: '/api/authenticate', + jwtStorageName: 'jhi-authenticationToken', + }, + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + async setupNodeEvents(on, config) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return (await import('./src/test/javascript/cypress/plugins/index')).default(on, config); + }, + baseUrl: 'http://localhost:8080/', + specPattern: 'src/test/javascript/cypress/e2e/**/*.cy.ts', + supportFile: 'src/test/javascript/cypress/support/index.ts', + experimentalRunAllSpecs: true, + }, +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/jest.conf.js b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/jest.conf.js new file mode 100644 index 0000000..969b41f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/jest.conf.js @@ -0,0 +1,29 @@ +const { pathsToModuleNameMapper } = require('ts-jest'); + +const { + compilerOptions: { paths = {}, baseUrl = './' }, +} = require('./tsconfig.json'); +const environment = require('./webpack/environment'); + +module.exports = { + transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$|dayjs/esm)'], + resolver: 'jest-preset-angular/build/resolvers/ng-jest-resolver.js', + globals: { + ...environment, + }, + roots: ['', `/${baseUrl}`], + modulePaths: [`/${baseUrl}`], + setupFiles: ['jest-date-mock'], + cacheDirectory: '/target/jest-cache', + coverageDirectory: '/target/test-results/', + moduleNameMapper: pathsToModuleNameMapper(paths, { prefix: `/${baseUrl}/` }), + reporters: [ + 'default', + ['jest-junit', { outputDirectory: '/target/test-results/', outputName: 'TESTS-results-jest.xml' }], + ['jest-sonar', { outputDirectory: './target/test-results/jest', outputName: 'TESTS-results-sonar.xml' }], + ], + testMatch: ['/src/main/webapp/app/**/@(*.)@(spec.ts)'], + testEnvironmentOptions: { + url: 'https://jhipster.tech', + }, +}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw new file mode 100755 index 0000000..8d937f4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw.cmd b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw.cmd new file mode 100644 index 0000000..c4586b5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/ngsw-config.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/ngsw-config.json new file mode 100644 index 0000000..8d57602 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/ngsw-config.json @@ -0,0 +1,21 @@ +{ + "$schema": "./node_modules/@angular/service-worker/config/schema.json", + "index": "/index.html", + "assetGroups": [ + { + "name": "app", + "installMode": "prefetch", + "resources": { + "files": ["/favicon.ico", "/index.html", "/manifest.webapp", "/*.css", "/*.js"] + } + }, + { + "name": "assets", + "installMode": "lazy", + "updateMode": "prefetch", + "resources": { + "files": ["/content/**", "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"] + } + } + ] +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw new file mode 100755 index 0000000..12a91e5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw @@ -0,0 +1,42 @@ +#!/bin/sh + +basedir=`dirname "$0"` + +if [ -f "$basedir/mvnw" ]; then + bindir="$basedir/target/node" + repodir="$basedir/target/node/node_modules" + installCommand="$basedir/mvnw -Pwebapp frontend:install-node-and-npm@install-node-and-npm" + + PATH="$basedir/$builddir/:$PATH" + NPM_EXE="$basedir/$builddir/node_modules/npm/bin/npm-cli.js" + NODE_EXE="$basedir/$builddir/node" +elif [ -f "$basedir/gradlew" ]; then + bindir="$basedir/build/node/bin" + repodir="$basedir/build/node/lib/node_modules" + installCommand="$basedir/gradlew npmSetup" +else + echo "Using npm installed globally" + exec npm "$@" +fi + +NPM_EXE="$repodir/npm/bin/npm-cli.js" +NODE_EXE="$bindir/node" + +if [ ! -x "$NPM_EXE" ] || [ ! -x "$NODE_EXE" ]; then + $installCommand || true +fi + +if [ -x "$NODE_EXE" ]; then + echo "Using node installed locally $($NODE_EXE --version)" + PATH="$bindir:$PATH" +else + NODE_EXE='node' +fi + +if [ ! -x "$NPM_EXE" ]; then + echo "Local npm not found, using npm installed globally" + npm "$@" +else + echo "Using npm installed locally $($NODE_EXE $NPM_EXE --version)" + $NODE_EXE $NPM_EXE "$@" +fi diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw.cmd b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw.cmd new file mode 100644 index 0000000..b6e7980 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/npmw.cmd @@ -0,0 +1,31 @@ +@echo off + +setlocal + +set NPMW_DIR=%~dp0 + +if exist "%NPMW_DIR%mvnw.cmd" ( + set NODE_EXE=^"^" + set NODE_PATH=%NPMW_DIR%target\node\ + set NPM_EXE=^"%NPMW_DIR%target\node\npm.cmd^" + set INSTALL_NPM_COMMAND=^"%NPMW_DIR%mvnw.cmd^" -Pwebapp frontend:install-node-and-npm@install-node-and-npm +) else ( + set NODE_EXE=^"%NPMW_DIR%build\node\bin\node.exe^" + set NODE_PATH=%NPMW_DIR%build\node\bin\ + set NPM_EXE=^"%NPMW_DIR%build\node\lib\node_modules\npm\bin\npm-cli.js^" + set INSTALL_NPM_COMMAND=^"%NPMW_DIR%gradlew.bat^" npmSetup +) + +if not exist %NPM_EXE% ( + call %INSTALL_NPM_COMMAND% +) + +if exist %NODE_EXE% ( + Rem execute local npm with local node, whilst adding local node location to the PATH for this CMD session + endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NODE_EXE% %NPM_EXE% %* +) else if exist %NPM_EXE% ( + Rem execute local npm, whilst adding local npm location to the PATH for this CMD session + endlocal & echo "%PATH%"|find /i "%NODE_PATH%;">nul || set "PATH=%NODE_PATH%;%PATH%" & call %NPM_EXE% %* +) else ( + call npm %* +) diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package-lock.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package-lock.json new file mode 100644 index 0000000..95a5502 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package-lock.json @@ -0,0 +1,28124 @@ +{ + "name": "app", + "version": "0.0.1-SNAPSHOT", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "app", + "version": "0.0.1-SNAPSHOT", + "license": "UNLICENSED", + "dependencies": { + "@angular/common": "16.2.11", + "@angular/compiler": "16.2.11", + "@angular/core": "16.2.11", + "@angular/forms": "16.2.11", + "@angular/localize": "16.2.11", + "@angular/platform-browser": "16.2.11", + "@angular/platform-browser-dynamic": "16.2.11", + "@angular/router": "16.2.11", + "@fortawesome/angular-fontawesome": "0.13.0", + "@fortawesome/fontawesome-svg-core": "6.4.2", + "@fortawesome/free-solid-svg-icons": "6.4.2", + "@ng-bootstrap/ng-bootstrap": "15.1.2", + "@ngx-translate/core": "15.0.0", + "@ngx-translate/http-loader": "8.0.0", + "@popperjs/core": "2.11.8", + "bootstrap": "5.3.2", + "dayjs": "1.11.10", + "ngx-infinite-scroll": "16.0.0", + "rxjs": "7.8.1", + "tslib": "2.6.2", + "zone.js": "0.13.3" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "16.0.1", + "@angular-builders/jest": "16.0.1", + "@angular-devkit/build-angular": "16.2.9", + "@angular-eslint/eslint-plugin": "16.2.0", + "@angular/cli": "16.2.9", + "@angular/compiler-cli": "16.2.11", + "@angular/service-worker": "16.2.11", + "@types/jest": "29.5.7", + "@types/node": "18.18.8", + "@typescript-eslint/eslint-plugin": "6.9.1", + "@typescript-eslint/parser": "6.9.1", + "browser-sync": "2.29.3", + "browser-sync-webpack-plugin": "2.3.0", + "buffer": "6.0.3", + "concurrently": "8.2.2", + "copy-webpack-plugin": "11.0.0", + "cypress": "13.4.0", + "eslint": "8.52.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-cypress": "2.15.1", + "eslint-webpack-plugin": "4.0.1", + "folder-hash": "4.0.4", + "generator-jhipster": "8.0.0", + "jest": "29.7.0", + "jest-date-mock": "1.0.8", + "jest-environment-jsdom": "29.7.0", + "jest-junit": "16.0.0", + "jest-preset-angular": "13.1.2", + "jest-sonar": "0.2.16", + "merge-jsons-webpack-plugin": "2.0.1", + "rimraf": "5.0.5", + "swagger-ui-dist": "5.9.1", + "ts-jest": "29.1.1", + "typescript": "5.1.6", + "wait-on": "7.0.1", + "webpack-bundle-analyzer": "4.9.1", + "webpack-merge": "5.10.0", + "webpack-notifier": "1.15.0" + }, + "engines": { + "node": ">=18.18.2" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-builders/custom-webpack": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-builders/custom-webpack/-/custom-webpack-16.0.1.tgz", + "integrity": "sha512-C6INC8UOYDcp8LJwNhE0m66yp+nZX50JdgGI8oRn7fqw3gO58qhDgXrR/8BCrSeC8eOx8WxSuvBJ6u+9dozhyw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", + "@angular-devkit/build-angular": "^16.0.0", + "@angular-devkit/core": "^16.0.0", + "lodash": "^4.17.15", + "ts-node": "^10.0.0", + "tsconfig-paths": "^4.1.0", + "webpack-merge": "^5.7.3" + }, + "engines": { + "node": "^14.20.0 || ^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0" + } + }, + "node_modules/@angular-builders/jest": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-16.0.1.tgz", + "integrity": "sha512-FuYkfy8JwdfTHevjgs8z18sXt0egcWbSSkefyM/QsGVkMHs+b4N4xzV20MQtyx0Yc6nJzuknIH5ZvDwLYUDPyQ==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": ">=0.1600.0 < 0.1700.0", + "@angular-devkit/core": "^16.0.0", + "jest-preset-angular": "13.1.1", + "lodash": "^4.17.15", + "tsconfig-paths": "^4.1.0" + }, + "engines": { + "node": "^14.20.0 || ^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular-devkit/build-angular": "^16.0.0", + "@angular/compiler-cli": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/platform-browser-dynamic": "^16.0.0", + "jest": ">=29" + } + }, + "node_modules/@angular-builders/jest/node_modules/jest-preset-angular": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-13.1.1.tgz", + "integrity": "sha512-X8i7icKt9U5uhj7YKqdEZm7ZZPvNFRxfBnU+9SALdIkHYJhwtlJ5/MUk9wo4f3lX2smOkIl9LPJUu1APO+11Jg==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "esbuild-wasm": ">=0.13.8", + "jest-environment-jsdom": "^29.0.0", + "jest-util": "^29.0.0", + "pretty-format": "^29.0.0", + "ts-jest": "^29.0.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "optionalDependencies": { + "esbuild": ">=0.13.8" + }, + "peerDependencies": { + "@angular-devkit/build-angular": ">=13.0.0 <17.0.0", + "@angular/compiler-cli": ">=13.0.0 <17.0.0", + "@angular/core": ">=13.0.0 <17.0.0", + "@angular/platform-browser-dynamic": ">=13.0.0 <17.0.0", + "jest": "^29.0.0", + "typescript": ">=4.4" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1602.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.10.tgz", + "integrity": "sha512-FwemQXh3edqA/S6zPpsqKei5v7gt0R0WpjJoAJaz+FOpfDwij1fwnKr88XINY8xcefTcQaTDQxJZheJShA/hHw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.10", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-16.2.9.tgz", + "integrity": "sha512-S1C4UYxRVyNt3C0wCxbT2jZ1dN5i37kS0mol3PQjbR8gQ0GQzHmzhjTBl1oImo8aouET9yhrk9etk65oat4mBQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.2.1", + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/build-webpack": "0.1602.9", + "@angular-devkit/core": "16.2.9", + "@babel/core": "7.22.9", + "@babel/generator": "7.22.9", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.22.6", + "@babel/plugin-proposal-async-generator-functions": "7.20.7", + "@babel/plugin-transform-async-to-generator": "7.22.5", + "@babel/plugin-transform-runtime": "7.22.9", + "@babel/preset-env": "7.22.9", + "@babel/runtime": "7.22.6", + "@babel/template": "7.22.5", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "16.2.9", + "@vitejs/plugin-basic-ssl": "1.0.1", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.14", + "babel-loader": "9.1.3", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.21.5", + "chokidar": "3.5.3", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.20", + "css-loader": "6.8.1", + "esbuild-wasm": "0.18.17", + "fast-glob": "3.3.1", + "guess-parser": "0.4.22", + "https-proxy-agent": "5.0.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "karma-source-map-support": "1.4.0", + "less": "4.1.3", + "less-loader": "11.1.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.1", + "mini-css-extract-plugin": "2.7.6", + "mrmime": "1.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "2.3.1", + "piscina": "4.0.0", + "postcss": "8.4.31", + "postcss-loader": "7.3.3", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.64.1", + "sass-loader": "13.3.2", + "semver": "7.5.4", + "source-map-loader": "4.0.1", + "source-map-support": "0.5.21", + "terser": "5.19.2", + "text-table": "0.2.0", + "tree-kill": "1.2.2", + "tslib": "2.6.1", + "vite": "4.4.7", + "webpack": "5.88.2", + "webpack-dev-middleware": "6.1.1", + "webpack-dev-server": "4.15.1", + "webpack-merge": "5.9.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.18.17" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "@angular/localize": "^16.0.0", + "@angular/platform-server": "^16.0.0", + "@angular/service-worker": "^16.0.0", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^16.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=4.9.3 <5.2" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/architect": { + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", + "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.9", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@angular-devkit/core": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/webpack-merge": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.9.0.tgz", + "integrity": "sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1602.9.tgz", + "integrity": "sha512-+3IxovfBPR2Vy730mGa0SVKkd5LQVom85gjXOs7WcnnnZmfc1q/BtFlqTgW1UWvTxP8IQdm7UYWVclQfL/WExw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1602.9", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^4.0.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/architect": { + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", + "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.9", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-webpack/node_modules/@angular-devkit/core": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.10.tgz", + "integrity": "sha512-eo7suLDjyu5bSlEr4TluYkFm4v2PVLSAPgnau8XHHlN5Yg4P/BZ00ve7LA7C9S1gzRSCrxQhK5ki4rnoFTo5zg==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.2.9.tgz", + "integrity": "sha512-lB51CGCILpcSI37CwKUAGDLxMqh7zmuRbiPo9s9mSkCM4ccqxFlaL+VFTq2/laneARD6aikpOHnkVm5myNzQPw==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.9", + "jsonc-parser": "3.2.0", + "magic-string": "0.30.1", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-16.2.0.tgz", + "integrity": "sha512-ct9orDYxkMl2+uvM7UBfgV28Dq57V4dEs+Drh7cD673JIMa6sXbgmd0QEtm8W3cmyK/jcTzmuoufxbH7hOxd6g==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-16.2.0.tgz", + "integrity": "sha512-zdiAIox1T+B71HL+A8m+1jWdU34nvPGLhCRw/uZNwHzknsF4tYzNQ9W7T/SC/g/2s1yT2yNosEVNJSGSFvunJg==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "16.2.0", + "@typescript-eslint/utils": "5.62.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-16.2.0.tgz", + "integrity": "sha512-NxMRwnlIgzmbJQfWkfd9y3Sz0hzjFdK5LH44i+3D5NhpPdZ6SzwHAjMYWoYsmmNQX5tlDXoicYF9Mz9Wz8DJ/A==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "16.2.0", + "@typescript-eslint/utils": "5.62.0" + }, + "peerDependencies": { + "eslint": "^7.20.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular/cli": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-16.2.9.tgz", + "integrity": "sha512-wkpV/Ni26LUeDmhee2TPXXEq3feEdZMSG8+nkfUK9kqIcxm0IjI1GLPeiVOX7aQobuKNe2cCAFNwsrXWjj+2og==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1602.9", + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", + "@schematics/angular": "16.2.9", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.1", + "inquirer": "8.2.4", + "jsonc-parser": "3.2.0", + "npm-package-arg": "10.1.0", + "npm-pick-manifest": "8.0.1", + "open": "8.4.2", + "ora": "5.4.1", + "pacote": "15.2.0", + "resolve": "1.22.2", + "semver": "7.5.4", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1602.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1602.9.tgz", + "integrity": "sha512-U3vfb/e2sFfg0D9FyyRBXRPP7g4FBFtGK8Q3JPmvAVsHHwi5AUFRNR7YBChB/T5TMNY077HcTyEirVh2FeUpdA==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.9", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/common": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-16.2.11.tgz", + "integrity": "sha512-h80WUR2OYlqxQy+4XgNtWT2vB+vZ6oCrFX/q8cU5jAvbvGQfJuH0zfcbSlUflStmAhk5/OT25F0mt96cqapEAw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-16.2.11.tgz", + "integrity": "sha512-9q/E3uurvoQbdTTWDyWCLpzmfJ4+et7SUca1/EljD/X7Xg2FNU5GpTMutBtWFL7wDyWk1oswivuq9/C4GVW7fA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/core": "16.2.11" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-16.2.11.tgz", + "integrity": "sha512-ZtZCXfkVBH78HUm2Byf+WX3Y6WzQK9EXYXNU/ni1rvSZ1vLNwieLDfWb/xwiO7QojrHZTym1RJ10jTMinTguqw==", + "dependencies": { + "@babel/core": "7.23.2", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.1.2", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler": "16.2.11", + "typescript": ">=4.9.3 <5.2" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/core/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==" + }, + "node_modules/@angular/compiler-cli/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==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/compiler-cli/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/core": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-16.2.11.tgz", + "integrity": "sha512-Jb+7/p1vczQRQ3iC1QxUS5cE4X1hPVAvbrFnyMpSx6Pq5o274v/lK6PvhUZrfKrp9FxFp9pN+WHjUqNFqOuJZg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.13.0" + } + }, + "node_modules/@angular/forms": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-16.2.11.tgz", + "integrity": "sha512-2powweUorehB1opfev6/sUeb3Bdey+Txq4gjI1Qdeo9c9OgtaKu6wK0KXgoism8HXXRFcGHMfS0dUVoDPVrtiQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.2.11", + "@angular/core": "16.2.11", + "@angular/platform-browser": "16.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/localize": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/localize/-/localize-16.2.11.tgz", + "integrity": "sha512-0wVJisUwUaSXAJXc3d+TEtYKYql1D866CJkEvDmWmZnhRCDtrdhvJH0rzkYPFMuK/9YYPH0VZeJQ0hmTHWFlpg==", + "dependencies": { + "@babel/core": "7.23.2", + "fast-glob": "3.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "localize-extract": "tools/bundles/src/extract/cli.js", + "localize-migrate": "tools/bundles/src/migrate/cli.js", + "localize-translate": "tools/bundles/src/translate/cli.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/compiler": "16.2.11", + "@angular/compiler-cli": "16.2.11" + } + }, + "node_modules/@angular/localize/node_modules/@babel/core": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/localize/node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/localize/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@angular/localize/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==" + }, + "node_modules/@angular/localize/node_modules/fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "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/@angular/localize/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/platform-browser": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-16.2.11.tgz", + "integrity": "sha512-gUptbI3lbaRg+L8rcTlxKtFunYmR/M/mm9/l9uRd+5qk2mnFI0+s/tzRoaq7K0XaRGKZiWLNTz6FTkviO1zo2g==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/animations": "16.2.11", + "@angular/common": "16.2.11", + "@angular/core": "16.2.11" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-16.2.11.tgz", + "integrity": "sha512-e+A7z6MUJaqC4Fdq7XQfIhAox3ZPM1lczM6G08fUKPbFDEe+c9i7C8YRLL+69BXDuG790btugIeOQcn5lnJcFg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.2.11", + "@angular/compiler": "16.2.11", + "@angular/core": "16.2.11", + "@angular/platform-browser": "16.2.11" + } + }, + "node_modules/@angular/router": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-16.2.11.tgz", + "integrity": "sha512-QTssqJue+xQ8M1gzmfJcIHPIpPOijVwGnXQjt7cnFggNe/CedOckLEzk2j7/6aC1b5aQKuZePPw6XMvk8ciilQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.2.11", + "@angular/core": "16.2.11", + "@angular/platform-browser": "16.2.11", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/service-worker": { + "version": "16.2.11", + "resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-16.2.11.tgz", + "integrity": "sha512-fsfnQq/NbAI4sxz1eSs7o1scB0ezawjj6rwV6uaqPNMvHq8fphauGTkACiIF6SBhD3p54lRzEH5whG6aei1Ofg==", + "dev": true, + "dependencies": { + "tslib": "^2.3.0" + }, + "bin": { + "ngsw-config": "ngsw-config.js" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": "16.2.11", + "@angular/core": "16.2.11" + } + }, + "node_modules/@assemblyscript/loader": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@assemblyscript/loader/-/loader-0.10.1.tgz", + "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==", + "dev": true + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "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==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.23.5.tgz", + "integrity": "sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz", + "integrity": "sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz", + "integrity": "sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.22.15", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz", + "integrity": "sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.22.5", + "@babel/template": "^7.22.15", + "@babel/types": "^7.22.19" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.5.tgz", + "integrity": "sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.5", + "@babel/types": "^7.23.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.5.tgz", + "integrity": "sha512-hOOqoiNXrmGdFbhgCzu6GiURxUgM27Xwd/aPuu8RfHEZPBzL1Z54okAHAQjXfcQNwvrlkAmAp4SlRTZ45vlthQ==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz", + "integrity": "sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.23.3.tgz", + "integrity": "sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-proposal-async-generator-functions": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.7.tgz", + "integrity": "sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead.", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-remap-async-to-generator": "^7.18.9", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-unicode-property-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", + "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "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-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.23.3.tgz", + "integrity": "sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", + "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "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, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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, + "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.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.23.3.tgz", + "integrity": "sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.23.4.tgz", + "integrity": "sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz", + "integrity": "sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-remap-async-to-generator": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.23.3.tgz", + "integrity": "sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.4.tgz", + "integrity": "sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.23.3.tgz", + "integrity": "sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.23.4.tgz", + "integrity": "sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.5.tgz", + "integrity": "sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20", + "@babel/helper-split-export-declaration": "^7.22.6", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz", + "integrity": "sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/template": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.3.tgz", + "integrity": "sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.23.3.tgz", + "integrity": "sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.23.3.tgz", + "integrity": "sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.23.4.tgz", + "integrity": "sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.23.3.tgz", + "integrity": "sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.23.4.tgz", + "integrity": "sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.23.3.tgz", + "integrity": "sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.23.3.tgz", + "integrity": "sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.23.4.tgz", + "integrity": "sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.23.3.tgz", + "integrity": "sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.23.4.tgz", + "integrity": "sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.23.3.tgz", + "integrity": "sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.3.tgz", + "integrity": "sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.3.tgz", + "integrity": "sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.3.tgz", + "integrity": "sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.23.3.tgz", + "integrity": "sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.23.3.tgz", + "integrity": "sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.23.4.tgz", + "integrity": "sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.23.4.tgz", + "integrity": "sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz", + "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.3", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.23.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.23.3.tgz", + "integrity": "sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-replace-supers": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.23.4.tgz", + "integrity": "sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.4.tgz", + "integrity": "sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz", + "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.23.3.tgz", + "integrity": "sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.23.4.tgz", + "integrity": "sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.23.3.tgz", + "integrity": "sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.23.3.tgz", + "integrity": "sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.23.3.tgz", + "integrity": "sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.9.tgz", + "integrity": "sha512-9KjBH61AGJetCPYp/IEyLEp47SyybZb0nDRpBvmtEkm+rUIwxdlKpyNHI1TmsGkeuLclJdleQHRZ8XLBnnh8CQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.4", + "babel-plugin-polyfill-corejs3": "^0.8.2", + "babel-plugin-polyfill-regenerator": "^0.5.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.23.3.tgz", + "integrity": "sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.23.3.tgz", + "integrity": "sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.23.3.tgz", + "integrity": "sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.23.3.tgz", + "integrity": "sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.23.3.tgz", + "integrity": "sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.23.3.tgz", + "integrity": "sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.23.3.tgz", + "integrity": "sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.23.3.tgz", + "integrity": "sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.23.3.tgz", + "integrity": "sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.9.tgz", + "integrity": "sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-plugin-utils": "^7.22.5", + "@babel/helper-validator-option": "^7.22.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.22.5", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.22.5", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.22.5", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.22.5", + "@babel/plugin-transform-async-generator-functions": "^7.22.7", + "@babel/plugin-transform-async-to-generator": "^7.22.5", + "@babel/plugin-transform-block-scoped-functions": "^7.22.5", + "@babel/plugin-transform-block-scoping": "^7.22.5", + "@babel/plugin-transform-class-properties": "^7.22.5", + "@babel/plugin-transform-class-static-block": "^7.22.5", + "@babel/plugin-transform-classes": "^7.22.6", + "@babel/plugin-transform-computed-properties": "^7.22.5", + "@babel/plugin-transform-destructuring": "^7.22.5", + "@babel/plugin-transform-dotall-regex": "^7.22.5", + "@babel/plugin-transform-duplicate-keys": "^7.22.5", + "@babel/plugin-transform-dynamic-import": "^7.22.5", + "@babel/plugin-transform-exponentiation-operator": "^7.22.5", + "@babel/plugin-transform-export-namespace-from": "^7.22.5", + "@babel/plugin-transform-for-of": "^7.22.5", + "@babel/plugin-transform-function-name": "^7.22.5", + "@babel/plugin-transform-json-strings": "^7.22.5", + "@babel/plugin-transform-literals": "^7.22.5", + "@babel/plugin-transform-logical-assignment-operators": "^7.22.5", + "@babel/plugin-transform-member-expression-literals": "^7.22.5", + "@babel/plugin-transform-modules-amd": "^7.22.5", + "@babel/plugin-transform-modules-commonjs": "^7.22.5", + "@babel/plugin-transform-modules-systemjs": "^7.22.5", + "@babel/plugin-transform-modules-umd": "^7.22.5", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.22.5", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5", + "@babel/plugin-transform-numeric-separator": "^7.22.5", + "@babel/plugin-transform-object-rest-spread": "^7.22.5", + "@babel/plugin-transform-object-super": "^7.22.5", + "@babel/plugin-transform-optional-catch-binding": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.22.6", + "@babel/plugin-transform-parameters": "^7.22.5", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.5", + "@babel/plugin-transform-property-literals": "^7.22.5", + "@babel/plugin-transform-regenerator": "^7.22.5", + "@babel/plugin-transform-reserved-words": "^7.22.5", + "@babel/plugin-transform-shorthand-properties": "^7.22.5", + "@babel/plugin-transform-spread": "^7.22.5", + "@babel/plugin-transform-sticky-regex": "^7.22.5", + "@babel/plugin-transform-template-literals": "^7.22.5", + "@babel/plugin-transform-typeof-symbol": "^7.22.5", + "@babel/plugin-transform-unicode-escapes": "^7.22.5", + "@babel/plugin-transform-unicode-property-regex": "^7.22.5", + "@babel/plugin-transform-unicode-regex": "^7.22.5", + "@babel/plugin-transform-unicode-sets-regex": "^7.22.5", + "@babel/preset-modules": "^0.1.5", + "@babel/types": "^7.22.5", + "babel-plugin-polyfill-corejs2": "^0.4.4", + "babel-plugin-polyfill-corejs3": "^0.8.2", + "babel-plugin-polyfill-regenerator": "^0.5.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz", + "integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.5.tgz", + "integrity": "sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.5", + "@babel/types": "^7.23.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.5.tgz", + "integrity": "sha512-BPssCHrBD+0YrxviOa3QzpqwhNIXKEtOa2jQrm4FlmkC2apYgRnQcmPWiGZDlGxiNtltnUFolMe8497Esry+jA==", + "dependencies": { + "@babel/types": "^7.23.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.5.tgz", + "integrity": "sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.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 + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "dev": true, + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "dev": true, + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "dev": true + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "dev": true + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "dev": true + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "optional": true, + "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, + "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, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@cypress/request": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "http-signature": "~1.3.6", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "performance-now": "^2.1.0", + "qs": "6.10.4", + "safe-buffer": "^5.1.2", + "tough-cookie": "^4.1.3", + "tunnel-agent": "^0.6.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@cypress/xvfb": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", + "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "lodash.once": "^4.1.1" + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "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==", + "dev": true, + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.17.tgz", + "integrity": "sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.17.tgz", + "integrity": "sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.17.tgz", + "integrity": "sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.17.tgz", + "integrity": "sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.17.tgz", + "integrity": "sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.17.tgz", + "integrity": "sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.17.tgz", + "integrity": "sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.17.tgz", + "integrity": "sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.17.tgz", + "integrity": "sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.17.tgz", + "integrity": "sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.17.tgz", + "integrity": "sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.17.tgz", + "integrity": "sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.17.tgz", + "integrity": "sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.17.tgz", + "integrity": "sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.17.tgz", + "integrity": "sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.17.tgz", + "integrity": "sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.17.tgz", + "integrity": "sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.17.tgz", + "integrity": "sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.17.tgz", + "integrity": "sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.17.tgz", + "integrity": "sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.17.tgz", + "integrity": "sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.17.tgz", + "integrity": "sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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": "^12.22.0 || ^14.17.0 || >=16.0.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, + "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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@faker-js/faker": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.2.0.tgz", + "integrity": "sha512-VacmzZqVxdWdf9y64lDOMZNDMM/FQdtM9IsaOPKOm2suYwEatb8VkdHqOzXcDnZbk7YDE2BmsJmy/2Hmkn563g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0", + "npm": ">=6.14.13" + } + }, + "node_modules/@fortawesome/angular-fontawesome": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@fortawesome/angular-fontawesome/-/angular-fontawesome-0.13.0.tgz", + "integrity": "sha512-gzSPRdveOXNO7NIiMgTyB46aiHG0i98KinnAEqHXi8qzraM/kCcHn/0y3f4MhemX6kftwsFli0IU8RyHmtXlSQ==", + "dependencies": { + "tslib": "^2.4.1" + }, + "peerDependencies": { + "@angular/core": "^16.0.0", + "@fortawesome/fontawesome-svg-core": "~1.2.27 || ~1.3.0-beta2 || ^6.1.0" + } + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", + "integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz", + "integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz", + "integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.2" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "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, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "dev": true + }, + "node_modules/@iarna/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "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/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "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/@isaacs/cliui/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, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", + "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", + "dev": true + }, + "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, + "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/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, + "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, + "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/console/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/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, + "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/@jest/console/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/console/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 + }, + "node_modules/@jest/console/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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/core/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/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, + "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/@jest/core/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/core/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 + }, + "node_modules/@jest/core/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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, + "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, + "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, + "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, + "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/reporters/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/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, + "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/@jest/reporters/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/reporters/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 + }, + "node_modules/@jest/reporters/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "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, + "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, + "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, + "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/transform/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/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, + "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/@jest/transform/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/transform/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 + }, + "node_modules/@jest/transform/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 + }, + "node_modules/@jest/transform/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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/@jest/types/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/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, + "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/@jest/types/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@jest/types/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 + }, + "node_modules/@jest/types/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1" + } + }, + "node_modules/@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==", + "dev": true + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", + "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "dev": true + }, + "node_modules/@ljharb/through": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.11.tgz", + "integrity": "sha512-ccfcIDlogiXNq5KcbAwbaO7lMh3Tm1i3khMPYpxlK8hH/W53zN81KM9coerRLOnTGu3nfXIniAmQbRI9OxbC0w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@ng-bootstrap/ng-bootstrap": { + "version": "15.1.2", + "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-15.1.2.tgz", + "integrity": "sha512-mM2yiGnt9o7KZLIFp8K1vjfmVfu7HR3d8dhH5SszfArbgn9DvvQ4P5D5TDGygzyBSzeyZe18p7I8rX8vgA6DKw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": "^16.0.0", + "@angular/core": "^16.0.0", + "@angular/forms": "^16.0.0", + "@angular/localize": "^16.0.0", + "@popperjs/core": "^2.11.6", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@ngtools/webpack": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-16.2.9.tgz", + "integrity": "sha512-rOclD7FfT4OSwVA0nDnULbJS6TORJ0+sQiuT2ebaNFErYr3LOm6Zut05tnmzFw8q1cePrILbG+xpnbggNr9Pyw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^16.0.0", + "typescript": ">=4.9.3 <5.2", + "webpack": "^5.54.0" + } + }, + "node_modules/@ngx-translate/core": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/core/-/core-15.0.0.tgz", + "integrity": "sha512-Am5uiuR0bOOxyoercDnAA3rJVizo4RRqJHo8N3RqJ+XfzVP/I845yEnMADykOHvM6HkVm4SZSnJBOiz0Anx5BA==", + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "node_modules/@ngx-translate/http-loader": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@ngx-translate/http-loader/-/http-loader-8.0.0.tgz", + "integrity": "sha512-SFMsdUcmHF5OdZkL1CHEoSAwbP5EbAOPTLLboOCRRoOg21P4GJx+51jxGdJeGve6LSKLf4Pay7BkTwmE6vxYlg==", + "engines": { + "node": "^16.13.0 || >=18.10.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0", + "@angular/core": ">=16.0.0", + "@ngx-translate/core": ">=15.0.0", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "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==", + "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==", + "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==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.0.tgz", + "integrity": "sha512-2yThA1Es98orMkpSLVqlDZAMPK3jHJhifP2gnNUdk1754uZ8yI5c+ulCoVG+WlntQA6MzhrURMXjSd9Z7dJ2/Q==", + "dev": true, + "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.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz", + "integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/https-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", + "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/agent/node_modules/socks-proxy-agent": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz", + "integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/@npmcli/arborist": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.2.2.tgz", + "integrity": "sha512-dIIzyhy1zS2dYPS8bdM/8qA8W2evQE9KENBxVOhFthm/2RKqf2ninRWQc8xfc5f1gsiTxTP20Y9flIfziHfSKA==", + "dev": true, + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^3.1.0", + "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/name-from-folder": "^2.0.0", + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/query": "^3.0.1", + "@npmcli/run-script": "^7.0.2", + "bin-links": "^4.0.1", + "cacache": "^18.0.0", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^7.0.1", + "json-parse-even-better-errors": "^3.0.0", + "json-stringify-nice": "^1.1.4", + "minimatch": "^9.0.0", + "nopt": "^7.0.0", + "npm-install-checks": "^6.2.0", + "npm-package-arg": "^11.0.1", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "npmlog": "^7.0.1", + "pacote": "^17.0.4", + "parse-conflict-json": "^3.0.0", + "proc-log": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^1.0.2", + "read-package-json-fast": "^3.0.2", + "semver": "^7.3.7", + "ssri": "^10.0.5", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@npmcli/git": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.3.tgz", + "integrity": "sha512-UZp9NwK+AynTrKvHn5k3KviW/hA5eENmFsu3iAPe7sWRt0lFUdsY/wXIYjpDFe7cdSNwOIzbObfwgt6eL5/2zw==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@npmcli/promise-spawn": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.0.tgz", + "integrity": "sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@npmcli/run-script": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.2.tgz", + "integrity": "sha512-Omu0rpA8WXvcGeY6DDzyRoY1i5DkCBkzyJ+m2u7PD6quzb0TvSqdIPOkTn8ZBOj7LbbcbMfZ3c5skwSu6m8y2w==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@sigstore/bundle": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz", + "integrity": "sha512-89uOo6yh/oxaU8AeOUnVrTdVMcGk9Q1hJa7Hkvalc6G3Z3CupWk4Xe9djSgJm9fMkH69s0P0cVHUoKSOemLdng==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@sigstore/sign": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.2.0.tgz", + "integrity": "sha512-AAbmnEHDQv6CSfrWA5wXslGtzLPtAtHZleKOgxdQYvx/s76Fk6T6ZVt7w2IGV9j1UrFeBocTTQxaXG2oRrDhYA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@sigstore/tuf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.2.0.tgz", + "integrity": "sha512-KKATZ5orWfqd9ZG6MN8PtCIx4eevWSuGRKQvofnWXRpyMyUEpmrzg5M5BrCpjM+NfZ0RbNGOh5tCz/P2uoRqOA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/@tufjs/models": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/are-we-there-yet": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-4.0.1.tgz", + "integrity": "sha512-2zuA+jpOYBRgoBCfa+fB87Rk0oGJjDX6pxGzqH6f33NzUhG25Xur6R0u0Z9VVAq8Z5JvQpQI6j6rtonuivC8QA==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^4.1.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/cacache": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.1.tgz", + "integrity": "sha512-g4Uf2CFZPaxtJKre6qr4zqLDOOPU7bNVhWjlNhvzc51xaTOx2noMOLhfFkTAqwtrAZAKQUuDfyjitzilpA8WsQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.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": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@npmcli/arborist/node_modules/gauge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-5.0.1.tgz", + "integrity": "sha512-CmykPMJGuNan/3S4kZOpvvPYSNqSHANiWnh9XcMU2pSjtBfF0XzZ2p1bFAxTbnFxyBuPxQYHhzwaoOmUdqzvxQ==", + "dev": 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": "^4.0.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/arborist/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/arborist/node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/arborist/node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@npmcli/arborist/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/@npmcli/arborist/node_modules/node-gyp": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.0.1.tgz", + "integrity": "sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npm-packlist": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.1.tgz", + "integrity": "sha512-MQpL27ZrsJQ2kiAuQPpZb5LtJwydNRnI15QWXsf3WHERu4rzjRj6Zju/My2fov7tLuu3Gle/uoIX/DDZ3u4O4Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz", + "integrity": "sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/npmlog": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-7.0.1.tgz", + "integrity": "sha512-uJ0YFk/mCQpLBt+bxN88AKd+gyqZvZDbtiNxk6Waqcj2aPRyfVx8ITawkyQynxUagInjdYT1+qj4NfA5KJJUxg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "^4.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^5.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/pacote": { + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.5.tgz", + "integrity": "sha512-TAE0m20zSDMnchPja9vtQjri19X3pZIyRpm2TJVeI+yU42leJBBDTRYhOcWFsPhaMxf+3iwQkFiKz16G9AEeeA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/read-package-json": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", + "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/arborist/node_modules/sigstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.1.0.tgz", + "integrity": "sha512-kPIj+ZLkyI3QaM0qX8V/nSsweYND3W448pwkDgS6CQ74MfhEkIR8ToK5Iyx46KJYRjseVcD3Rp9zAmUAj6ZjPw==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.1.0", + "@sigstore/tuf": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/tuf-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.1.0.tgz", + "integrity": "sha512-eD7YPPjVlMzdggrOeE8zwoegUaG/rt6Bt3jwoQPunRiNVzgcCE009UDFJKJjG+Gk9wFu6W/Vi+P5d/5QpdD9jA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/arborist/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.0.tgz", + "integrity": "sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", + "integrity": "sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^6.0.0", + "lru-cache": "^7.4.4", + "npm-pick-manifest": "^8.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.0.2.tgz", + "integrity": "sha512-xACzLPhnfD51GKvTOOuNX2/V4G4mz9/1I2MfDoye9kBM3RYe5g2YbscsaGoTlaWqkxeiapBWyseULVKpSVHtKQ==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "lib/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.4.tgz", + "integrity": "sha512-Z0TbvXkRbacjFFLpVpV0e2mheCh+WzQpcqL+4xp49uNJOxOnIAPZyXtUxZ5Qn3QBTGKA11Exjd9a5411rBrhDg==", + "dev": true, + "dependencies": { + "@npmcli/name-from-folder": "^2.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0", + "read-package-json-fast": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/metavuln-calculator": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.0.0.tgz", + "integrity": "sha512-Pw0tyX02VkpqlIQlG2TeiJNsdrecYeUU0ubZZa9pi3N37GCsxI+en43u4hYFdq+eSx1A9a9vwFAUyqEtKFsbHQ==", + "dev": true, + "dependencies": { + "cacache": "^18.0.0", + "json-parse-even-better-errors": "^3.0.0", + "pacote": "^17.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@npmcli/git": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.3.tgz", + "integrity": "sha512-UZp9NwK+AynTrKvHn5k3KviW/hA5eENmFsu3iAPe7sWRt0lFUdsY/wXIYjpDFe7cdSNwOIzbObfwgt6eL5/2zw==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@npmcli/promise-spawn": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.0.tgz", + "integrity": "sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@npmcli/run-script": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.2.tgz", + "integrity": "sha512-Omu0rpA8WXvcGeY6DDzyRoY1i5DkCBkzyJ+m2u7PD6quzb0TvSqdIPOkTn8ZBOj7LbbcbMfZ3c5skwSu6m8y2w==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@sigstore/bundle": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.1.0.tgz", + "integrity": "sha512-89uOo6yh/oxaU8AeOUnVrTdVMcGk9Q1hJa7Hkvalc6G3Z3CupWk4Xe9djSgJm9fMkH69s0P0cVHUoKSOemLdng==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@sigstore/sign": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.2.0.tgz", + "integrity": "sha512-AAbmnEHDQv6CSfrWA5wXslGtzLPtAtHZleKOgxdQYvx/s76Fk6T6ZVt7w2IGV9j1UrFeBocTTQxaXG2oRrDhYA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@sigstore/tuf": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.2.0.tgz", + "integrity": "sha512-KKATZ5orWfqd9ZG6MN8PtCIx4eevWSuGRKQvofnWXRpyMyUEpmrzg5M5BrCpjM+NfZ0RbNGOh5tCz/P2uoRqOA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.1", + "tuf-js": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/@tufjs/models": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.0.tgz", + "integrity": "sha512-c8nj8BaOExmZKO2DXhDfegyhSGcG9E/mPN3U13L+/PsoWm1uaGiHHjxqSHQiasDBQwDA3aHuw9+9spYAP1qvvg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/cacache": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.1.tgz", + "integrity": "sha512-g4Uf2CFZPaxtJKre6qr4zqLDOOPU7bNVhWjlNhvzc51xaTOx2noMOLhfFkTAqwtrAZAKQUuDfyjitzilpA8WsQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.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": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/make-fetch-happen": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz", + "integrity": "sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/node-gyp": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.0.1.tgz", + "integrity": "sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/nopt": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.0.tgz", + "integrity": "sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/npm-packlist": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.1.tgz", + "integrity": "sha512-MQpL27ZrsJQ2kiAuQPpZb5LtJwydNRnI15QWXsf3WHERu4rzjRj6Zju/My2fov7tLuu3Gle/uoIX/DDZ3u4O4Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/npm-registry-fetch": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.1.0.tgz", + "integrity": "sha512-PQCELXKt8Azvxnt5Y85GseQDJJlglTFM9L9U9gkv2y4e9s0k3GVDdOx3YoB6gm2Do0hlkzC39iCGXby+Wve1Bw==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "17.0.5", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.5.tgz", + "integrity": "sha512-TAE0m20zSDMnchPja9vtQjri19X3pZIyRpm2TJVeI+yU42leJBBDTRYhOcWFsPhaMxf+3iwQkFiKz16G9AEeeA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^7.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^16.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/read-package-json": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.0.tgz", + "integrity": "sha512-uL4Z10OKV4p6vbdvIXB+OzhInYtIozl/VxUBPgNkBuUi2DeRonnuspmaVAMcrkmfjKGNmRndyQAbE7/AmzGwFg==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/sigstore": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.1.0.tgz", + "integrity": "sha512-kPIj+ZLkyI3QaM0qX8V/nSsweYND3W448pwkDgS6CQ74MfhEkIR8ToK5Iyx46KJYRjseVcD3Rp9zAmUAj6ZjPw==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.1.0", + "@sigstore/protobuf-specs": "^0.2.1", + "@sigstore/sign": "^2.1.0", + "@sigstore/tuf": "^2.1.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/tuf-js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.1.0.tgz", + "integrity": "sha512-eD7YPPjVlMzdggrOeE8zwoegUaG/rt6Bt3jwoQPunRiNVzgcCE009UDFJKJjG+Gk9wFu6W/Vi+P5d/5QpdD9jA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.0", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/metavuln-calculator/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@npmcli/move-file/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/move-file/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@npmcli/name-from-folder": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", + "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.0.0.tgz", + "integrity": "sha512-OI2zdYBLhQ7kpNPaJxiflofYIpkNLi+lnGdzqUOfRmCF3r2l1nadcjtCYMJKv/Utm/ZtlffaUuTiAktPHbc17g==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/@npmcli/git": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.3.tgz", + "integrity": "sha512-UZp9NwK+AynTrKvHn5k3KviW/hA5eENmFsu3iAPe7sWRt0lFUdsY/wXIYjpDFe7cdSNwOIzbObfwgt6eL5/2zw==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^3.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/@npmcli/promise-spawn": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.0.tgz", + "integrity": "sha512-wBqcGsMELZna0jDblGd7UXgOby45TQaMWmbFwWX+SEotk4HV6zG2t6rT9siyLhPk4P6YYqgfL1UO8nMWDBVJXQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/package-json/node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/npm-package-arg": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.1.tgz", + "integrity": "sha512-M7s1BD4NxdAvBKUPqqRW957Xwcl/4Zvo8Aj+ANrzvIPzGJZElrH7Z//rSaec2ORcND6FHHLnZeY8qgTpXDMFQQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/npm-pick-manifest": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.0.tgz", + "integrity": "sha512-VfvRSs/b6n9ol4Qb+bDwNGUXutpy76x6MARw/XssevE0TnctIKcmklJZM5Z7nqs5z5aW+0S63pgCNbpkUNNXBg==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-6.0.2.tgz", + "integrity": "sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==", + "dev": true, + "dependencies": { + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/query": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.0.1.tgz", + "integrity": "sha512-0jE8iHBogf/+bFDj+ju6/UMLbJ39c8h6nSe6qile+dB7PJ0iV3gNqcb2vtt6WWCBrxv9uAjzUT/8vroluulidA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-6.0.2.tgz", + "integrity": "sha512-NCcr1uQo1k5U+SYlnIrbAh3cxy+OQT1VtqiAbxdymSlptbzBb62AjH2xXgjNCoP073hoa1CfCAcwoZ8k96C4nA==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/promise-spawn": "^6.0.0", + "node-gyp": "^9.0.0", + "read-package-json-fast": "^3.0.0", + "which": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", + "integrity": "sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", + "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3" + } + }, + "node_modules/@octokit/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", + "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", + "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", + "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", + "dev": true, + "dependencies": { + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "12.11.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz", + "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==", + "dev": true + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "2.21.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz", + "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.40.0" + }, + "peerDependencies": { + "@octokit/core": ">=2" + } + }, + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", + "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", + "dev": true, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "5.16.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz", + "integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.39.0", + "deprecation": "^2.3.1" + }, + "peerDependencies": { + "@octokit/core": ">=3" + } + }, + "node_modules/@octokit/request": { + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", + "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" + } + }, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", + "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "dev": true, + "dependencies": { + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } + }, + "node_modules/@octokit/rest": { + "version": "18.12.0", + "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", + "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", + "dev": true, + "dependencies": { + "@octokit/core": "^3.5.1", + "@octokit/plugin-paginate-rest": "^2.16.8", + "@octokit/plugin-request-log": "^1.0.4", + "@octokit/plugin-rest-endpoint-methods": "^5.12.0" + } + }, + "node_modules/@octokit/types": { + "version": "6.41.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz", + "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^12.11.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/utils": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.2.tgz", + "integrity": "sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "fast-glob": "^3.3.0", + "is-glob": "^4.0.3", + "open": "^9.1.0", + "picocolors": "^1.0.0", + "tslib": "^2.6.0" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pkgr/utils/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pkgr/utils/node_modules/open": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz", + "integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==", + "dev": true, + "dependencies": { + "default-browser": "^4.0.0", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", + "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", + "dev": true, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", + "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", + "dev": true, + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.2.2.tgz", + "integrity": "sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==", + "dev": true, + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.24", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.24.tgz", + "integrity": "sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==", + "dev": true + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@schematics/angular": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-16.2.9.tgz", + "integrity": "sha512-uiU2YbZRVHgk1N1DDsek/5CKhfpZ8myJYNJk8eHV5LswnXOP3aqvH23VhneaAgOYwK5fISC7eMG0pLVKMvFfZQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "16.2.9", + "@angular-devkit/schematics": "16.2.9", + "jsonc-parser": "3.2.0" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "16.2.9", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.2.9.tgz", + "integrity": "sha512-dcHWjHBNGm3yCeNz19y8A1At4KgyC6XHNnbFL0y+nnZYiaESXjUoXJYKASedI6A+Bpl0HNq2URhH6bL6Af3+4w==", + "dev": true, + "dependencies": { + "ajv": "8.12.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.2.0", + "picomatch": "2.3.1", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^16.14.0 || >=18.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true + }, + "node_modules/@sigstore/bundle": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-1.1.0.tgz", + "integrity": "sha512-PFutXEy0SmQxYI4texPw3dd2KewuNqv7OuK1ZFtY2fM754yhvG2KdgwIhRnoEE2uHdtdGNQ8s0lb94dW9sELog==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.2.1.tgz", + "integrity": "sha512-XTWVxnWJu+c1oCshMLwnKvz8ZQJJDVOlciMfgpJBQbThVjKTCG8dwyhgLngBD2KN0ap9F/gOV8rFDEx8uh7R2A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-1.0.0.tgz", + "integrity": "sha512-INxFVNQteLtcfGmcoldzV6Je0sbbfh9I16DM4yJPw3j5+TFP8X6uIiA18mvpEa9yyeycAKgPmOA3X9hVdVTPUA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "make-fetch-happen": "^11.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@sigstore/sign/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@sigstore/sign/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@sigstore/sign/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/@sigstore/sign/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@sigstore/tuf": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-1.0.3.tgz", + "integrity": "sha512-2bRovzs0nJZFlCN3rXirE4gwxCn97JNjMmwpecqlbgV9WcxX7WRuIrgzx/X7Ib7MYRbyUTpBYE0s2x6AmZXnlg==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.2.0", + "tuf-js": "^1.1.7" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.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 + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "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, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "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 + }, + "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 + }, + "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 + }, + "node_modules/@tufjs/canonical-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-1.0.0.tgz", + "integrity": "sha512-QTnf++uxunWvG2z3UFNzAoQPHxnSXOwtaI3iJ+AohhV+5vONuArPjJE7aPXPVXfXJsqrVbZBu9b81AJoSd09IQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-1.0.4.tgz", + "integrity": "sha512-qaGV9ltJP0EO25YfFUPhxRVK0evXFIAGicsVXuRim4Ed9cjPxYhNnNJ49SFmbeLgtxpslIkX317IgpfcHPVj/A==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "1.0.0", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "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.7", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.7.tgz", + "integrity": "sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==", + "dev": true, + "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, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.4", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.4.tgz", + "integrity": "sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "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==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@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==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ejs": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.4.tgz", + "integrity": "sha512-fnM/NjByiWdSRJRrmGxgqOSAnmOnsvX1QcNYk5TVyIIj+7ZqOKMb9gQa4OIl/lil2w/8TiTWV+nz3q8yqxez/w==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.44.8", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.8.tgz", + "integrity": "sha512-4K8GavROwhrYl2QXDXm0Rv9epkA8GBFu0EI+XrrnnuCl7u8CWBRusX7fXJfanhZTDWSAL24gDI/UqXyUM0Injw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.41", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.41.tgz", + "integrity": "sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@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, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/inquirer": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.7.tgz", + "integrity": "sha512-Q0zyBupO6NxGRZut/JdmqYKOnN95Eg5V8Csg3PGKkP+FnvsUZx1jAyK7fztIszxxMuoBA6E3KXWvdZVXIpx60g==", + "dev": true, + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "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 + }, + "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, + "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, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.7", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.7.tgz", + "integrity": "sha512-HLyetab6KVPSiF+7pFcUyMeLsx25LDNDemw9mGsJBkai/oouwrjTycocSDYopMEwFhN2Y4s9oPyOCZNofgSt2g==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.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 + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.14.200", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", + "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "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==", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.18.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.8.tgz", + "integrity": "sha512-OLGBaaK5V3VRBS1bAkMVP2/W9B+H8meUfl866OrMNQqt7wDgdpWPp5o6gmIc9pB+lIQHSq4ZL8ypeH1vPxcPaQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", + "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/normalize-package-data": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", + "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", + "dev": true + }, + "node_modules/@types/qs": { + "version": "6.9.10", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.10.tgz", + "integrity": "sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==", + "dev": true + }, + "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 + }, + "node_modules/@types/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "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, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", + "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", + "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", + "dev": true + }, + "node_modules/@types/sizzle": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", + "integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==", + "dev": true + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "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 + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/@types/vinyl": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.11.tgz", + "integrity": "sha512-vPXzCLmRp74e9LsP8oltnWKTH+jBwt86WgRUb4Pc9Lf3pkMVGyvIo2gm9bODeGfCay2DBB/hAWDuvf07JcK4rw==", + "dev": true, + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "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 + }, + "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==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.9.1", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.0.1.tgz", + "integrity": "sha512-pcub+YbFtFhaGRTo1832FQHQSHvMrlb43974e2eS8EKleR3p1cDdkJFPci1UhwkEf1J9Bz+wKBSzqpKp7nNj2A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@wessberg/ts-evaluator": { + "version": "0.0.27", + "resolved": "https://registry.npmjs.org/@wessberg/ts-evaluator/-/ts-evaluator-0.0.27.tgz", + "integrity": "sha512-7gOpVm3yYojUp/Yn7F4ZybJRxyqfMNf0LXK5KJiawbPfL0XTsJV+0mgrEDjOIR6Bi0OYk2Cyg4tjFu1r8MCZaA==", + "deprecated": "this package has been renamed to ts-evaluator. Please install ts-evaluator instead", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "jsdom": "^16.4.0", + "object-path": "^0.11.5", + "tslib": "^2.0.3" + }, + "engines": { + "node": ">=10.1.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/wessberg/ts-evaluator?sponsor=1" + }, + "peerDependencies": { + "typescript": ">=3.2.x || >= 4.x" + } + }, + "node_modules/@wessberg/ts-evaluator/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@wessberg/ts-evaluator/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, + "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/@wessberg/ts-evaluator/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@wessberg/ts-evaluator/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 + }, + "node_modules/@wessberg/ts-evaluator/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@wessberg/ts-evaluator/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/@yeoman/adapter": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@yeoman/adapter/-/adapter-1.4.0.tgz", + "integrity": "sha512-JroPWaZ8fALkfRt1FVM8/jz0kGOviVkKaCR4y0EM9Si2B9UD4UySGLCrjyUWeWBGqgr2iGAQ0ehoHjRAlyzsFg==", + "dev": true, + "dependencies": { + "@types/inquirer": "^9.0.3", + "chalk": "^5.2.0", + "inquirer": "^9.2.2", + "log-symbols": "^5.1.0", + "ora": "^6.3.1", + "p-queue": "^7.3.4", + "text-table": "^0.2.0" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + }, + "peerDependencies": { + "@yeoman/types": "^1.1.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@yeoman/adapter/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@yeoman/adapter/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 + }, + "node_modules/@yeoman/adapter/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/figures": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", + "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0", + "is-unicode-supported": "^1.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/figures/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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer": { + "version": "9.2.12", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", + "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "dev": true, + "dependencies": { + "@ljharb/through": "^2.3.11", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "figures": "^5.0.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/log-symbols/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, + "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/@yeoman/adapter/node_modules/inquirer/node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/inquirer/node_modules/ora/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, + "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/@yeoman/adapter/node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.1.tgz", + "integrity": "sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==", + "dev": true, + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dev": true, + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/node_modules/ora/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, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/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, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@yeoman/adapter/node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/adapter/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@yeoman/adapter/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@yeoman/conflicter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@yeoman/conflicter/-/conflicter-2.0.0.tgz", + "integrity": "sha512-DhxzWfHXg+W3AGyWM35L2o4GkQbUcT30f2+l6/2sZGwQcUPyTIR9RDyxrV9pf6YlwUJwvKjL2jLdB2QlJ1mKbg==", + "dev": true, + "dependencies": { + "@types/node": "^16.18.28", + "@yeoman/transform": "^1.2.0", + "binary-extensions": "^2.2.0", + "cli-table": "^0.3.11", + "dateformat": "^5.0.3", + "diff": "^5.1.0", + "isbinaryfile": "^5.0.0", + "mem-fs-editor": "^11.0.0", + "minimatch": "^9.0.0", + "p-transform": "^4.1.3", + "pretty-bytes": "^6.1.0", + "textextensions": "^5.16.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "@yeoman/types": "^1.0.0", + "mem-fs": "^4.0.0" + } + }, + "node_modules/@yeoman/conflicter/node_modules/@types/node": { + "version": "16.18.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz", + "integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg==", + "dev": true + }, + "node_modules/@yeoman/conflicter/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@yeoman/conflicter/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@yeoman/conflicter/node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@yeoman/namespace": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@yeoman/namespace/-/namespace-1.0.0.tgz", + "integrity": "sha512-+HcGOOoLSP3+Hb3xA3TpYDiSsmok/boJtbd4bhNfKGDp9/bXkSBpK0Bqmydl0ulo4rUGwiY95eVtP2sLpoDGlA==", + "dev": true, + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/@yeoman/transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@yeoman/transform/-/transform-1.2.0.tgz", + "integrity": "sha512-evb/+2XMEBoHr4BxBeFkjeVTgTS4Qe7VH8DmzZ9kgJK7C7ACPAhW/qBdsKKP1sb5MoeITSaJSVFnc8S1fjZmcw==", + "dev": true, + "dependencies": { + "@types/node": "^16.18.28", + "minimatch": "^9.0.0", + "readable-stream": "^4.3.0" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/@yeoman/transform/node_modules/@types/node": { + "version": "16.18.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz", + "integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg==", + "dev": true + }, + "node_modules/@yeoman/transform/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@yeoman/transform/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@yeoman/types": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@yeoman/types/-/types-1.1.1.tgz", + "integrity": "sha512-BErzFIs5ETmbwSCBT24ZL+TVjFik+CbrHpeO1DUeWwwx3nfE6IJOp5hAwSdnASsPQAbtaBHrt/if6Q/3HTCS3g==", + "dev": true, + "dependencies": { + "@types/node": "^16.18.26" + }, + "acceptDependencies": { + "mem-fs": "^4.0.0-beta.1", + "mem-fs-editor": ">=10.0.2" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + }, + "peerDependencies": { + "@types/inquirer": "^9.0.3", + "mem-fs": "^3.0.0", + "mem-fs-editor": "^10.0.2" + }, + "peerDependenciesMeta": { + "inquirer": { + "optional": true + }, + "mem-fs": { + "optional": true + }, + "mem-fs-editor": { + "optional": true + } + } + }, + "node_modules/@yeoman/types/node_modules/@types/node": { + "version": "16.18.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz", + "integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg==", + "dev": true + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "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, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.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==", + "dev": true, + "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==", + "dev": 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==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "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==", + "dev": true + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "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" + } + ] + }, + "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==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "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, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-differ": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-4.0.0.tgz", + "integrity": "sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", + "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dev": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true + }, + "node_modules/async-each-series": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/async-each-series/-/async-each-series-0.1.1.tgz", + "integrity": "sha512-p4jj6Fws4Iy2m0iCmI2am2ZNZCgbdgE+P8F/8csmn2vx7ixXrO2zGcuNsD46X5uZSVecmkEy/M06X2vG8KD6dQ==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "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 + }, + "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, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atomically": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.0.2.tgz", + "integrity": "sha512-Xfmb4q5QV7uqTlVdMSTtO5eF4DCHfNOdaPyKlbFShkzeNP+3lj3yjjcbdjSmEY4+pDBKJ9g26aP+ImTe88UHoQ==", + "dev": true, + "dependencies": { + "stubborn-fs": "^1.2.5", + "when-exit": "^2.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "node_modules/axios": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", + "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/axios/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==", + "dev": 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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/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, + "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/babel-jest/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/babel-jest/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 + }, + "node_modules/babel-jest/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-jest/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "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, + "dependencies": { + "@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/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, + "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-plugin-polyfill-corejs2": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz", + "integrity": "sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.4.3", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.8.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.6.tgz", + "integrity": "sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3", + "core-js-compat": "^3.33.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.3.tgz", + "integrity": "sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.4.3" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@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-top-level-await": "^7.8.3" + }, + "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, + "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==", + "dev": 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==", + "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" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/before-after-hook": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz", + "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==", + "dev": true + }, + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bin-links": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.3.tgz", + "integrity": "sha512-obsRaULtJurnfox/MDwgq6Yo9kzbv1CPTk/1/s7Z/61Lezc8IKkFCOXNeVLXz0456WRzBQmSsDWlai2tIhBsfA==", + "dev": true, + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/bin-links/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/bin-links/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/binaryextensions": { + "version": "4.19.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-4.19.0.tgz", + "integrity": "sha512-DRxnVbOi/1OgA5pA9EDiRT8gvVYeqfuN7TmPfLyt6cyho3KbHCi3EtDQf39TTmGDrR5dZ9CspdXhPkL/j/WGbg==", + "dev": true, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/blob-util": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", + "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", + "dev": true + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "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.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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==", + "dev": true + }, + "node_modules/body-parser/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bonjour-service": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.1.1.tgz", + "integrity": "sha512-Z/5lQRMOG9k7W+FkeGTNjh7htqn/2LMnfOvBZ8pynNZCM9MwkQkI3zeI4oz09uWdcgmgHugVvBqxGg4VQJ5PCg==", + "dev": true, + "dependencies": { + "array-flatten": "^2.1.2", + "dns-equal": "^1.0.0", + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/bootstrap": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.2.tgz", + "integrity": "sha512-D32nmNWiQHo94BKHLmOrdjlL05q1c8oxbtBphQFb9Z5to6eGRDCm0QgeaZ4zFBHzfg2++rqa2JkqCcxDy0sH0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bplist-parser": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", + "integrity": "sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.44" + }, + "engines": { + "node": ">= 5.10.0" + } + }, + "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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "node_modules/browser-sync": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.29.3.tgz", + "integrity": "sha512-NiM38O6XU84+MN+gzspVmXV2fTOoe+jBqIBx3IBdhZrdeURr6ZgznJr/p+hQ+KzkKEiGH/GcC4SQFSL0jV49bg==", + "dev": true, + "dependencies": { + "browser-sync-client": "^2.29.3", + "browser-sync-ui": "^2.29.3", + "bs-recipes": "1.3.4", + "chalk": "4.1.2", + "chokidar": "^3.5.1", + "connect": "3.6.6", + "connect-history-api-fallback": "^1", + "dev-ip": "^1.0.1", + "easy-extender": "^2.3.4", + "eazy-logger": "^4.0.1", + "etag": "^1.8.1", + "fresh": "^0.5.2", + "fs-extra": "3.0.1", + "http-proxy": "^1.18.1", + "immutable": "^3", + "localtunnel": "^2.0.1", + "micromatch": "^4.0.2", + "opn": "5.3.0", + "portscanner": "2.2.0", + "raw-body": "^2.3.2", + "resp-modifier": "6.0.2", + "rx": "4.1.0", + "send": "0.16.2", + "serve-index": "1.9.1", + "serve-static": "1.13.2", + "server-destroy": "1.0.1", + "socket.io": "^4.4.1", + "ua-parser-js": "^1.0.33", + "yargs": "^17.3.1" + }, + "bin": { + "browser-sync": "dist/bin.js" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/browser-sync-client": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-client/-/browser-sync-client-2.29.3.tgz", + "integrity": "sha512-4tK5JKCl7v/3aLbmCBMzpufiYLsB1+UI+7tUXCCp5qF0AllHy/jAqYu6k7hUF3hYtlClKpxExWaR+rH+ny07wQ==", + "dev": true, + "dependencies": { + "etag": "1.8.1", + "fresh": "0.5.2", + "mitt": "^1.1.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/browser-sync-ui": { + "version": "2.29.3", + "resolved": "https://registry.npmjs.org/browser-sync-ui/-/browser-sync-ui-2.29.3.tgz", + "integrity": "sha512-kBYOIQjU/D/3kYtUIJtj82e797Egk1FB2broqItkr3i4eF1qiHbFCG6srksu9gWhfmuM/TNG76jMfzAdxEPakg==", + "dev": true, + "dependencies": { + "async-each-series": "0.1.1", + "chalk": "4.1.2", + "connect-history-api-fallback": "^1", + "immutable": "^3", + "server-destroy": "1.0.1", + "socket.io-client": "^4.4.1", + "stream-throttle": "^0.1.3" + } + }, + "node_modules/browser-sync-ui/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync-ui/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, + "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/browser-sync-ui/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync-ui/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 + }, + "node_modules/browser-sync-ui/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync-ui/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync-webpack-plugin": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/browser-sync-webpack-plugin/-/browser-sync-webpack-plugin-2.3.0.tgz", + "integrity": "sha512-MDvuRrTCtoL11dTdwMymo9CNJvYxJoW67gOO61cThfzHNX40S5WcBU+0bVQ86ll7r7aNpNgyzxF7RtnXMTDbyA==", + "dev": true, + "dependencies": { + "lodash": "^4" + }, + "peerDependencies": { + "browser-sync": "^2", + "webpack": "^1 || ^2 || ^3 || ^4 || ^5" + } + }, + "node_modules/browser-sync/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/browser-sync/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, + "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/browser-sync/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/browser-sync/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 + }, + "node_modules/browser-sync/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-sync/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", + "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "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" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001565", + "electron-to-chromium": "^1.4.601", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "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, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bs-recipes": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/bs-recipes/-/bs-recipes-1.3.4.tgz", + "integrity": "sha512-BXvDkqhDNxXEjeGM8LFkSbR+jzmP/CYpCiVKYn+soB1dDldeU15EBNDkwVXndKuX35wnNUaPd0qSoQEAkmQtMw==", + "dev": true + }, + "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, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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==", + "dev": true, + "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 + }, + "node_modules/builtins": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz", + "integrity": "sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==", + "dev": true, + "dependencies": { + "semver": "^7.0.0" + } + }, + "node_modules/bundle-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz", + "integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==", + "dev": true, + "dependencies": { + "run-applescript": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "17.1.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-17.1.4.tgz", + "integrity": "sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^7.7.1", + "minipass": "^7.0.3", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/cacache/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cachedir": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", + "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001566", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001566.tgz", + "integrity": "sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==", + "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" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "dev": true + }, + "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==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/check-more-types": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", + "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "dev": true, + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "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" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "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" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-table": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz", + "integrity": "sha512-IqLQi4lO0nIB4tcdTpN4LCB9FI3uqrJZK7RC515EnhZ6qBaglkIgICb1wjeAqpdoOabm1+SuQtkXIPdYC93jhQ==", + "dev": true, + "dependencies": { + "colors": "1.0.3" + }, + "engines": { + "node": ">= 0.2.0" + } + }, + "node_modules/cli-table3": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", + "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "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==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "dev": true + }, + "node_modules/cmd-shim": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.2.tgz", + "integrity": "sha512-+FFYbB0YLaAkhkcrjkyNLYDiOsFSfRjwjY19LXk/psmMx1z00xlCv7hhQoTGXXIKi+YXHL/iiFo8NqMVQX9nOw==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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, + "engines": { + "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 + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "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==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "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==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "dependencies": { + "color-name": "^1.0.0", + "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==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dev": true, + "dependencies": { + "color": "^3.1.3", + "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, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-ancestor-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", + "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", + "dev": true + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/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 + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/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, + "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/concurrently/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/concurrently/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 + }, + "node_modules/concurrently/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/concurrently/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/conf": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-12.0.0.tgz", + "integrity": "sha512-fIWyWUXrJ45cHCIQX+Ck1hrZDIf/9DR0P0Zewn3uNht28hbt5OfGUq8rRWsxi96pZWPyBEd0eY9ama01JTaknA==", + "dev": true, + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^2.1.1", + "atomically": "^2.0.2", + "debounce-fn": "^5.1.2", + "dot-prop": "^8.0.2", + "env-paths": "^3.0.0", + "json-schema-typed": "^8.0.1", + "semver": "^7.5.4", + "uint8array-extras": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/connect": { + "version": "3.6.6", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", + "integrity": "sha512-OO7axMmPpu/2XuX1+2Yrg0ddju31B6xLZMWkJ5rYBu4YRmRVlOjvlY6kw2FJKiAzyxGwnrDUAG4s1Pf0sbBMCQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.0", + "parseurl": "~1.3.2", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/core-js-compat": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.34.0.tgz", + "integrity": "sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==", + "dev": true, + "dependencies": { + "browserslist": "^4.22.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/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==", + "dev": true + }, + "node_modules/cosmiconfig/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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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, + "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-jest/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/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, + "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/create-jest/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/create-jest/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 + }, + "node_modules/create-jest/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-jest/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "node_modules/critters": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.20.tgz", + "integrity": "sha512-CImNRorKOl5d8TWcnAz5n5izQ6HFsvz29k327/ELy6UFcmbiZNOsinaKvzv16WZR0P6etfSWYzE47C4/56B3Uw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "pretty-bytes": "^5.3.0" + } + }, + "node_modules/critters/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/critters/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, + "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/critters/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/critters/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 + }, + "node_modules/critters/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/critters/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-loader": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.8.1.tgz", + "integrity": "sha512-xDAXtEVGlD0gJ07iclwWVkLoZOpEvAWaSyf6W18S2pOC//K8+qUDIx8IIT3D+HjnmkJPQeesOPv5aiUaJsCM2g==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.21", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.3", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "node_modules/cypress": { + "version": "13.4.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.4.0.tgz", + "integrity": "sha512-KeWNC9xSHG/ewZURVbaQsBQg2mOKw4XhjJZFKjWbEjgZCdxpPXLpJnfq5Jns1Gvnjp6AlnIfpZfWFlDgVKXdWQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@cypress/request": "^3.0.0", + "@cypress/xvfb": "^1.2.4", + "@types/node": "^18.17.5", + "@types/sinonjs__fake-timers": "8.1.1", + "@types/sizzle": "^2.3.2", + "arch": "^2.2.0", + "blob-util": "^2.0.2", + "bluebird": "^3.7.2", + "buffer": "^5.6.0", + "cachedir": "^2.3.0", + "chalk": "^4.1.0", + "check-more-types": "^2.24.0", + "cli-cursor": "^3.1.0", + "cli-table3": "~0.6.1", + "commander": "^6.2.1", + "common-tags": "^1.8.0", + "dayjs": "^1.10.4", + "debug": "^4.3.4", + "enquirer": "^2.3.6", + "eventemitter2": "6.4.7", + "execa": "4.1.0", + "executable": "^4.1.1", + "extract-zip": "2.0.1", + "figures": "^3.2.0", + "fs-extra": "^9.1.0", + "getos": "^3.2.1", + "is-ci": "^3.0.0", + "is-installed-globally": "~0.4.0", + "lazy-ass": "^1.6.0", + "listr2": "^3.8.3", + "lodash": "^4.17.21", + "log-symbols": "^4.0.0", + "minimist": "^1.2.8", + "ospath": "^1.2.2", + "pretty-bytes": "^5.6.0", + "process": "^0.11.10", + "proxy-from-env": "1.0.0", + "request-progress": "^3.0.0", + "semver": "^7.5.3", + "supports-color": "^8.1.1", + "tmp": "~0.2.1", + "untildify": "^4.0.0", + "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + } + }, + "node_modules/cypress/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cypress/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cypress/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, + "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/cypress/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cypress/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 + }, + "node_modules/cypress/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, + "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/cypress/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/cypress/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, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/cypress/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/cypress/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, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", + "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.3", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dateformat": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-5.0.3.tgz", + "integrity": "sha512-Kvr6HmPXUMerlLcLF+Pwq3K7apHpYmGDVqrxcDasBg86UcKeTSNWbEzU8bwdXnxnR44FtMhJAxI4Bov6Y/KUfA==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, + "node_modules/debounce-fn": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-5.1.2.tgz", + "integrity": "sha512-Sr4SdOZ4vw6eQDvPYNxHogvrxmCIld/VenC5JbNrFwMiwd7lY/Z18ZFfo+EWNG4DD9nFlAujWAo/wGuOPHmy5A==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/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==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "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", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "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 + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-browser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz", + "integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==", + "dev": true, + "dependencies": { + "bundle-name": "^3.0.0", + "default-browser-id": "^3.0.0", + "execa": "^7.1.1", + "titleize": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz", + "integrity": "sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==", + "dev": true, + "dependencies": { + "bplist-parser": "^0.2.0", + "untildify": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/default-browser/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/default-browser/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/default-gateway/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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/default-gateway/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway/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, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.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", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/deprecation": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", + "dev": true + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "node_modules/detect-indent": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.1.tgz", + "integrity": "sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/dev-ip": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dev-ip/-/dev-ip-1.0.1.tgz", + "integrity": "sha512-LmVkry/oDShEgSZPNgqCIp2/TlqtExeGmymru3uCELnfyjY11IzpAproLYs+1X88fXO6DBoYP3ul2Xo2yz2j6A==", + "dev": true, + "bin": { + "dev-ip": "lib/dev-ip.js" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", + "dev": true, + "engines": { + "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, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dockerfile-ast": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/dockerfile-ast/-/dockerfile-ast-0.6.1.tgz", + "integrity": "sha512-m3rH2qHHU2pSTCppXgJT+1KLxhvkdROOxVPof5Yz4IPGSw6K+x0B0/RFdYgXN5zsIUTlbOSRyfDCv3/uVhnNmg==", + "dev": true, + "dependencies": { + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domexception": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", + "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "dependencies": { + "webidl-conversions": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", + "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-prop": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-8.0.2.tgz", + "integrity": "sha512-xaBe6ZT4DHPkg0k4Ytbvn5xoxgpG0jOS1dYxSOwAHPuNLjP3/OzN0gH55SrLqpx8cBfSaVt91lXYkApjb+nYdQ==", + "dev": true, + "dependencies": { + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-prop/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dot-properties": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dot-properties/-/dot-properties-1.0.1.tgz", + "integrity": "sha512-cjIHHKlf2dPINJ5Io3lPocWvWmthXn3ztqyHVzUfufRiCiPECb0oiEqEGbEGaunFZtcMvwgUcxP9CTpLG4KCsA==", + "dev": true + }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-extender": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/easy-extender/-/easy-extender-2.3.4.tgz", + "integrity": "sha512-8cAwm6md1YTiPpOvDULYJL4ZS6WfM5/cTeVVh4JsvyYZAoqlRVUpHL9Gr5Fy7HA6xcSZicUia3DeAgO3Us8E+Q==", + "dev": true, + "dependencies": { + "lodash": "^4.17.10" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/eazy-logger": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eazy-logger/-/eazy-logger-4.0.1.tgz", + "integrity": "sha512-2GSFtnnC6U4IEKhEI7+PvdxrmjJ04mdsj3wHZTFiw0tUtG4HCWzTr13ZYTk8XOGnA1xQMaDljoBOYlk3D/MMSw==", + "dev": true, + "dependencies": { + "chalk": "4.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eazy-logger/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eazy-logger/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, + "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/eazy-logger/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eazy-logger/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 + }, + "node_modules/eazy-logger/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eazy-logger/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dev": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.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==", + "dev": true + }, + "node_modules/ejs": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", + "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.609", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz", + "integrity": "sha512-ihiCP7PJmjoGNuLpl7TjNA8pCQWu09vGyjlPYw1Rqww4gvNuCcmvl+44G+2QyJ6S2K4o+wbTS++Xz0YN8Q9ERw==" + }, + "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, + "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", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "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==", + "dev": true, + "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==", + "dev": true, + "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", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.3.tgz", + "integrity": "sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "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/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "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/enhanced-resolve": { + "version": "5.15.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", + "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "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==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.17.tgz", + "integrity": "sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.17", + "@esbuild/android-arm64": "0.18.17", + "@esbuild/android-x64": "0.18.17", + "@esbuild/darwin-arm64": "0.18.17", + "@esbuild/darwin-x64": "0.18.17", + "@esbuild/freebsd-arm64": "0.18.17", + "@esbuild/freebsd-x64": "0.18.17", + "@esbuild/linux-arm": "0.18.17", + "@esbuild/linux-arm64": "0.18.17", + "@esbuild/linux-ia32": "0.18.17", + "@esbuild/linux-loong64": "0.18.17", + "@esbuild/linux-mips64el": "0.18.17", + "@esbuild/linux-ppc64": "0.18.17", + "@esbuild/linux-riscv64": "0.18.17", + "@esbuild/linux-s390x": "0.18.17", + "@esbuild/linux-x64": "0.18.17", + "@esbuild/netbsd-x64": "0.18.17", + "@esbuild/openbsd-x64": "0.18.17", + "@esbuild/sunos-x64": "0.18.17", + "@esbuild/win32-arm64": "0.18.17", + "@esbuild/win32-ia32": "0.18.17", + "@esbuild/win32-x64": "0.18.17" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.18.17", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.18.17.tgz", + "integrity": "sha512-9OHGcuRzy+I8ziF9FzjfKLWAPbvi0e/metACVg9k6bK+SI4FFxeV6PcZsz8RIVaMD4YNehw+qj6UMR3+qj/EuQ==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "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==", + "dev": true + }, + "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==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "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/escodegen/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/escodegen/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==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint": { + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", + "integrity": "sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-node/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, + "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/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-cypress": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.15.1.tgz", + "integrity": "sha512-eLHLWP5Q+I4j2AWepYq0PgFEei9/s5LvjuSqWrxurkg1YZ8ltxdvMNmdSf0drnsNo57CTgYY/NIHHLRSWejR7w==", + "dev": true, + "dependencies": { + "globals": "^13.20.0" + }, + "peerDependencies": { + "eslint": ">= 3.2.1" + } + }, + "node_modules/eslint-plugin-cypress/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-cypress/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.0.tgz", + "integrity": "sha512-QPOO5NO6Odv5lpoTkddtutccQjysJuFxoPS7fAHO+9m9udNHvTCPSAMW9zGAYj8lAIdr40I8yPCdUYrncXtrwg==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/eslint-plugin-import/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-import/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/eslint-plugin-unused-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-3.0.0.tgz", + "integrity": "sha512-sduiswLJfZHeeBJ+MQaG+xYzSWdRXoSw61DpU13mzWumCkR0ufD0HmO4kdNokjrkluMHpj/7PJeN35pgbhW3kw==", + "dev": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^6.0.0", + "eslint": "^8.0.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/eslint-plugin": { + "optional": true + } + } + }, + "node_modules/eslint-rule-composer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", + "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-webpack-plugin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-4.0.1.tgz", + "integrity": "sha512-fUFcXpui/FftGx3NzvWgLZXlLbu+m74sUxGEgxgoxYcUtkIQbS6SdNNZkS99m5ycb23TfoNYrDpp1k/CK5j6Hw==", + "dev": true, + "dependencies": { + "@types/eslint": "^8.37.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "eslint": "^8.0.0", + "webpack": "^5.0.0" + } + }, + "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, + "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/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/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==", + "dev": true + }, + "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, + "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/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/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 + }, + "node_modules/eslint/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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 + }, + "node_modules/eslint/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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "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==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/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, + "engines": { + "node": ">=4.0" + } + }, + "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, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter-asyncresource": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/eventemitter-asyncresource/-/eventemitter-asyncresource-1.0.0.tgz", + "integrity": "sha512-39F7TBIV0G7gTelxwbEqnwhp90eqCPON1k0NwNfwhgKn4Co4ybUbj2pECcXT0B3ztRKZ7Pw1JujUUgmQJHcVAQ==", + "dev": true + }, + "node_modules/eventemitter2": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", + "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", + "dev": true + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/executable": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", + "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", + "dev": true, + "dependencies": { + "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "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/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/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==", + "dev": true + }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/express/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/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==", + "dev": true + }, + "node_modules/express/node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/external-editor/node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "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==", + "dev": true, + "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/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "dev": true, + "engines": [ + "node >=0.6.0" + ] + }, + "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 + }, + "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==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "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 + }, + "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 + }, + "node_modules/fast-xml-parser": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz", + "integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "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, + "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==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "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, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "integrity": "sha512-ejnvM9ZXYzp6PUPUyQBMBf0Co5VX2gr5H2VQe2Ui2jWXNlxv+PYZo8wpAymJNJdLsG1R4p+M4aynF8KuoUEwRw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/first-chunk-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-5.0.0.tgz", + "integrity": "sha512-WdHo4ejd2cG2Dl+sLkW79SctU7mUQDfr4s1i26ffOZRs5mgv+BRttIM9gwcq0rDbemo0KlpVPaa3LBVLqPXzcQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flat-cache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/fly-import": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/fly-import/-/fly-import-0.4.0.tgz", + "integrity": "sha512-sgIZHb7m0eze7hneKzuzXPLWs3RD9vK93Kqc4hvm/eiptVLbYHz4zZp0ckUAXUCoxq5/yGjfh7OUUJOWP9VqGA==", + "dev": true, + "dependencies": { + "@npmcli/arborist": "^7.2.0", + "env-paths": "^3.0.0", + "registry-auth-token": "^5.0.2", + "registry-url": "^6.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "dev": true + }, + "node_modules/folder-hash": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/folder-hash/-/folder-hash-4.0.4.tgz", + "integrity": "sha512-zEyYH+UsHEfJJcCRSf9ai5I4CTZwZ8ObONRuEI5hcEmJY5pS0FUWKruX9mMnYJrgC7MlPFDYnGsK1R+WFYjLlQ==", + "dev": true, + "dependencies": { + "debug": "^4.3.3", + "minimatch": "~5.1.2" + }, + "bin": { + "folder-hash": "bin/folder-hash" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/folder-hash/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/folder-hash/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", + "integrity": "sha512-V3Z3WZWVUYd8hoCL5xfXJCaHWYzmtwW5XWYSlLgERi8PWd8bx1kUHUk8L1BT57e49oKnDDD180mjfrHc1yA9rg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^3.0.0", + "universalify": "^0.1.0" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/fs-monkey": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "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", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "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==", + "dev": 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/generator-jhipster": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/generator-jhipster/-/generator-jhipster-8.0.0.tgz", + "integrity": "sha512-cWwJFibdYFrJaud1u/wrb4x5gXnjfbI1lTlVQ4/xBEKxpm1PIzgL6OZu2l2tbVmVhYB1ETVlDOQylCVSuZMAoQ==", + "dev": true, + "dependencies": { + "@faker-js/faker": "8.2.0", + "@iarna/toml": "3.0.0", + "@types/ejs": "3.1.4", + "@types/lodash": "4.14.200", + "@typescript-eslint/eslint-plugin": "6.9.1", + "@typescript-eslint/parser": "6.9.1", + "@yeoman/adapter": "1.4.0", + "@yeoman/conflicter": "2.0.0", + "@yeoman/namespace": "1.0.0", + "@yeoman/transform": "1.2.0", + "@yeoman/types": "1.1.1", + "axios": "1.6.0", + "chalk": "5.3.0", + "chevrotain": "11.0.3", + "commander": "11.1.0", + "conf": "12.0.0", + "debug": "4.3.4", + "didyoumean": "1.2.2", + "dockerfile-ast": "0.6.1", + "dot-properties": "1.0.1", + "ejs": "3.1.9", + "escape-string-regexp": "5.0.0", + "eslint": "8.52.0", + "eslint-plugin-import": "2.29.0", + "eslint-plugin-unused-imports": "3.0.0", + "execa": "8.0.1", + "fast-xml-parser": "4.3.2", + "glob": "10.3.10", + "insight": "0.11.1", + "isbinaryfile": "5.0.0", + "java-lint": "0.1.0", + "js-yaml": "4.1.0", + "latest-version": "7.0.0", + "lodash-es": "4.17.21", + "mem-fs": "4.0.0", + "mem-fs-editor": "11.0.0", + "minimatch": "9.0.3", + "normalize-path": "3.0.0", + "os-locale": "6.0.2", + "p-queue": "7.4.1", + "p-transform": "4.1.5", + "parse-gitignore": "2.0.0", + "pluralize": "8.0.0", + "prettier": "3.0.3", + "prettier-plugin-java": "2.3.1", + "prettier-plugin-packagejson": "2.4.6", + "prettier-plugin-properties": "0.3.0", + "progress": "2.0.3", + "randexp": "0.5.3", + "run-async": "3.0.0", + "semver": "7.5.4", + "simple-git": "3.20.0", + "type-fest": "4.6.0", + "uuid": "9.0.1", + "winston": "3.11.0", + "yaml": "2.3.3", + "yeoman-environment": "4.0.0", + "yeoman-generator": "7.1.0" + }, + "bin": { + "jhipster": "dist/cli/jhipster.cjs" + }, + "engines": { + "node": "^18.13.0 || >= 20.6.1", + "npm": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/generator-jhipster" + }, + "peerDependencies": { + "yeoman-test": "8.2.0" + }, + "peerDependenciesMeta": { + "yeoman-test": { + "optional": true + } + } + }, + "node_modules/generator-jhipster/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==", + "dev": true + }, + "node_modules/generator-jhipster/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/generator-jhipster/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/generator-jhipster/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/generator-jhipster/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/generator-jhipster/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/generator-jhipster/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/generator-jhipster/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/generator-jhipster/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/generator-jhipster/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/generator-jhipster/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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==", + "engines": { + "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==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "funding": { + "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, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/getos": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", + "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", + "dev": true, + "dependencies": { + "async": "^3.2.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/git-hooks-list": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/git-hooks-list/-/git-hooks-list-3.1.0.tgz", + "integrity": "sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==", + "dev": true, + "funding": { + "url": "https://github.com/fisker/git-hooks-list?sponsor=1" + } + }, + "node_modules/github-username": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-7.0.0.tgz", + "integrity": "sha512-mzCjmmR1LcNf0/qvkJRO63di2lUUuEoRuCqzflq8wrpAajOo7zLSXOTTuj2qr1DhFY2pruw5JLw/CokZU/3ilg==", + "dev": true, + "dependencies": { + "@octokit/rest": "^18.12.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "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==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/glob/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/global-dirs/node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "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/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/got/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/grouped-queue": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz", + "integrity": "sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", + "dev": true + }, + "node_modules/guess-parser": { + "version": "0.4.22", + "resolved": "https://registry.npmjs.org/guess-parser/-/guess-parser-0.4.22.tgz", + "integrity": "sha512-KcUWZ5ACGaBM69SbqwVIuWGoSAgD+9iJnchR9j/IarVI1jHVeXv+bUXBIMeqVMSKt3zrn0Dgf9UpcOEpPBLbSg==", + "dev": true, + "dependencies": { + "@wessberg/ts-evaluator": "0.0.27" + }, + "peerDependencies": { + "typescript": ">=3.7.5" + } + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dev": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/har-validator/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, + "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/har-validator/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 + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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==", + "dev": true + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hdr-histogram-js": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz", + "integrity": "sha512-Hkn78wwzWHNCp2uarhzQ2SGFLU3JY8SBDDd3TAABK4fc30wm+MuPOrg5QVFVfkKOQd6Bfz3ukJEI+q9sXEkK1g==", + "dev": true, + "dependencies": { + "@assemblyscript/loader": "^0.10.1", + "base64-js": "^1.2.0", + "pako": "^1.0.3" + } + }, + "node_modules/hdr-histogram-percentiles-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hdr-histogram-percentiles-obj/-/hdr-histogram-percentiles-obj-3.0.0.tgz", + "integrity": "sha512-7kIufnBqdsBGcSZLPJwqHT3yhk1QTsSlFsVD3kx5ixH/AlgBs9yM1q6DPhXZ8f8gtdqgh7N7/5btRLpQsS2gHw==", + "dev": true + }, + "node_modules/hosted-git-info": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", + "integrity": "sha512-r0EI+HBMcXadMrugk0GCQ+6BQV39PiWAZVfq7oIckeGiN7sjRGyQxPdft3nQekFTCQbYxLBH+/axZMeH8UX6+w==", + "dev": true, + "dependencies": { + "lru-cache": "^7.5.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/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 + }, + "node_modules/hpack.js/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, + "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/hpack.js/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 + }, + "node_modules/hpack.js/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, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", + "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^1.0.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/html-entities": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", + "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "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 + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "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==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true, + "engines": { + "node": ">=8.12.0" + } + }, + "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==", + "dev": 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", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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" + } + ] + }, + "node_modules/ignore": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.4.tgz", + "integrity": "sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ignore-walk/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/ignore-walk/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "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/import-local/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, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": 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==", + "dev": 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==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "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, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", + "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.4.1", + "run-async": "^2.4.0", + "rxjs": "^7.5.5", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/inquirer/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/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, + "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/inquirer/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/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 + }, + "node_modules/inquirer/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/inquirer/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/insight": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/insight/-/insight-0.11.1.tgz", + "integrity": "sha512-TBcZ0qC9dgdmcxL93OoqkY/RZXJtIi0i07phX/QyYk2ysmJtZex59dgTj4Doq50N9CG9dLRe/RIudc/5CCoFNw==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "chalk": "^4.1.1", + "conf": "^10.0.1", + "inquirer": "^6.3.1", + "lodash.debounce": "^4.0.8", + "os-name": "^4.0.1", + "request": "^2.88.0", + "tough-cookie": "^4.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=12.20" + } + }, + "node_modules/insight/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/insight/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/insight/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/insight/node_modules/atomically": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz", + "integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==", + "dev": true, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/insight/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, + "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/insight/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "node_modules/insight/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/insight/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 + }, + "node_modules/insight/node_modules/conf": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/conf/-/conf-10.2.0.tgz", + "integrity": "sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==", + "dev": true, + "dependencies": { + "ajv": "^8.6.3", + "ajv-formats": "^2.1.1", + "atomically": "^1.7.0", + "debounce-fn": "^4.0.0", + "dot-prop": "^6.0.1", + "env-paths": "^2.2.1", + "json-schema-typed": "^7.0.3", + "onetime": "^5.1.2", + "pkg-up": "^3.1.0", + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/insight/node_modules/debounce-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/debounce-fn/-/debounce-fn-4.0.0.tgz", + "integrity": "sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/insight/node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/insight/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/insight/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/insight/node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/insight/node_modules/inquirer/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, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/inquirer/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, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/inquirer/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, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/insight/node_modules/inquirer/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 + }, + "node_modules/insight/node_modules/inquirer/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/inquirer/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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/json-schema-typed": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", + "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==", + "dev": true + }, + "node_modules/insight/node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/insight/node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", + "dev": true + }, + "node_modules/insight/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/restore-cursor/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/restore-cursor/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/insight/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/insight/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/insight/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/insight/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/insight/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/invert-kv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz", + "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sindresorhus/invert-kv?sponsor=1" + } + }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", + "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "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==", + "engines": { + "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, + "engines": { + "node": ">=6" + } + }, + "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==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-like": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", + "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", + "dev": true, + "dependencies": { + "lodash.isfinite": "^3.3.2" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", + "dev": true + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.0.tgz", + "integrity": "sha512-UDdnyGvMajJUWCkib7Cei/dvyJrrvo4FIrsvSFWdPpXSUorzXrDJ0S+X5Q4ZlasfPjca4yqCNNsjbCeiy8FFeg==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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, + "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/istanbul-lib-instrument/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, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/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, + "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/jake/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jake/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 + }, + "node_modules/jake/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/java-lint": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/java-lint/-/java-lint-0.1.0.tgz", + "integrity": "sha512-J7lL0CvYOwbLY7Gu00vY3n36WH/DtgWwXIlpVRY2ioI3tuCmD9zFvVA1x2qFrHZyJUUzfwtkLNEa3tfwVslJ2Q==", + "dev": true, + "dependencies": { + "java-parser": "^2.0.4" + }, + "engines": { + "node": "^16.13.0 || ^18.13.0 || >= 20.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/marcelo-boveto-shima" + } + }, + "node_modules/java-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-2.2.0.tgz", + "integrity": "sha512-20/Rpuv4FnzTNgWkJBqs2M4qwBuPLMqOqNiwU5j7vKvrw4Ej+re8Aoc92KbDFb61M1u/Sd4Ygst+CpEcKXvhBQ==", + "dev": true, + "dependencies": { + "chevrotain": "6.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/java-parser/node_modules/chevrotain": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-6.5.0.tgz", + "integrity": "sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==", + "dev": true, + "dependencies": { + "regexp-to-ast": "0.4.0" + } + }, + "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, + "dependencies": { + "@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": "^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-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, + "dependencies": { + "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/jest-changed-files/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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/jest-changed-files/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-changed-files/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, + "engines": { + "node": ">=10.17.0" + } + }, + "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, + "dependencies": { + "@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/jest-circus/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/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, + "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/jest-circus/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-circus/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 + }, + "node_modules/jest-circus/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-circus/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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": "^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-cli/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/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, + "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/jest-cli/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-cli/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 + }, + "node_modules/jest-cli/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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": "^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/jest-config/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/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, + "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/jest-config/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-config/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 + }, + "node_modules/jest-config/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-config/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-date-mock": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/jest-date-mock/-/jest-date-mock-1.0.8.tgz", + "integrity": "sha512-0Lyp+z9xvuNmLbK+5N6FOhSiBeux05Lp5bbveFBmYo40Aggl2wwxFoIrZ+rOWC8nDNcLeBoDd2miQdEDSf3iQw==", + "dev": true + }, + "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, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/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, + "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/jest-diff/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-diff/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 + }, + "node_modules/jest-diff/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/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, + "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/jest-each/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-each/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 + }, + "node_modules/jest-each/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-each/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/jest-environment-jsdom/node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/jest-environment-jsdom/node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/jest-environment-jsdom/node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-jsdom/node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jest-environment-jsdom/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==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-environment-jsdom/node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-jsdom/node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/jest-environment-jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jest-environment-jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-environment-jsdom/node_modules/ws": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.0.tgz", + "integrity": "sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw==", + "dev": true, + "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/jest-environment-jsdom/node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-junit": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz", + "integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==", + "dev": true, + "dependencies": { + "mkdirp": "^1.0.4", + "strip-ansi": "^6.0.1", + "uuid": "^8.3.2", + "xml": "^1.0.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "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, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/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, + "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/jest-matcher-utils/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-matcher-utils/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 + }, + "node_modules/jest-matcher-utils/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-matcher-utils/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/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, + "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/jest-message-util/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-message-util/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 + }, + "node_modules/jest-message-util/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-message-util/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-preset-angular": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/jest-preset-angular/-/jest-preset-angular-13.1.2.tgz", + "integrity": "sha512-kuzuIch/YYTMaMnuGDaiZEu++Bjc5WskOAmMwqWO0Grpcd0SulqTOV70Gz6Q/ZOQuMye+LS4KPyIVfqnJr2e3g==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "esbuild-wasm": ">=0.13.8", + "jest-environment-jsdom": "^29.0.0", + "jest-util": "^29.0.0", + "pretty-format": "^29.0.0", + "ts-jest": "^29.0.0" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0" + }, + "optionalDependencies": { + "esbuild": ">=0.13.8" + }, + "peerDependencies": { + "@angular-devkit/build-angular": ">=13.0.0 <17.0.0", + "@angular/compiler-cli": ">=13.0.0 <17.0.0", + "@angular/core": ">=13.0.0 <17.0.0", + "@angular/platform-browser-dynamic": ">=13.0.0 <17.0.0", + "jest": "^29.0.0", + "typescript": ">=4.4" + } + }, + "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, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/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, + "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/jest-resolve/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-resolve/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 + }, + "node_modules/jest-resolve/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-resolve/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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-runner/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/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, + "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/jest-runner/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runner/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 + }, + "node_modules/jest-runner/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner/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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-runner/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, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runner/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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-runtime/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/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, + "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/jest-runtime/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-runtime/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 + }, + "node_modules/jest-runtime/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runtime/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/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, + "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/jest-snapshot/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-snapshot/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 + }, + "node_modules/jest-snapshot/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-sonar": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/jest-sonar/-/jest-sonar-0.2.16.tgz", + "integrity": "sha512-ES6Z9BbIVDELtbz+/b6pv41B2qOfp38cQpoCLqei21FtlkG/GzhyQ0M3egEIM+erpJOkpRKM8Tc8/YQtHdiTXA==", + "dev": true, + "dependencies": { + "entities": "4.3.0", + "strip-ansi": "6.0.1" + } + }, + "node_modules/jest-sonar/node_modules/entities": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.3.0.tgz", + "integrity": "sha512-/iP1rZrSEJ0DTlPiX+jbzlA3eVkY/e8L8SozroF395fIqE3TYF/Nz7YOMAawta+vLmyJ/hkGNNPcSbMADCCXbg==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/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, + "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/jest-util/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-util/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 + }, + "node_modules/jest-util/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-util/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-validate/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, + "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/jest-validate/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-validate/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 + }, + "node_modules/jest-validate/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-validate/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "@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": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-watcher/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-watcher/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, + "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/jest-watcher/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/jest-watcher/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 + }, + "node_modules/jest-watcher/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-watcher/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/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, + "engines": { + "node": ">=8" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dev": true, + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "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==" + }, + "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, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/jsdom": { + "version": "16.7.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz", + "integrity": "sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.2.4", + "acorn-globals": "^6.0.0", + "cssom": "^0.4.4", + "cssstyle": "^2.3.0", + "data-urls": "^2.0.0", + "decimal.js": "^10.2.1", + "domexception": "^2.0.1", + "escodegen": "^2.0.0", + "form-data": "^3.0.0", + "html-encoding-sniffer": "^2.0.1", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^2.0.0", + "webidl-conversions": "^6.1.0", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^8.5.0", + "ws": "^7.4.6", + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/jsdom/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "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 + }, + "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 + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": 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 + }, + "node_modules/json-schema-typed": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.1.tgz", + "integrity": "sha512-XQmWYj2Sm4kn4WeTYvmpKEbyPsL7nBsb647c7pMe6l02/yx2+Jfc4dT6UZkEXnIUb5LhD55r2HPsJ1milQ4rDg==", + "dev": true + }, + "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 + }, + "node_modules/json-stringify-nice": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", + "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", + "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", + "integrity": "sha512-oBko6ZHlubVB5mRFkur5vgYR1UyqX+S6Y/oCfLhqNdcc2fYFlDpIoNc7AfKS1KOGcnNAkvsr0grLck9ANM815w==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/just-diff": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", + "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", + "dev": true + }, + "node_modules/just-diff-apply": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", + "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", + "dev": true + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "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, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/klona": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", + "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "dev": true + }, + "node_modules/latest-version": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", + "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", + "dev": true, + "dependencies": { + "package-json": "^8.1.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/lazy-ass": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", + "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", + "dev": true, + "engines": { + "node": "> 0.8" + } + }, + "node_modules/lcid": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz", + "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==", + "dev": true, + "dependencies": { + "invert-kv": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/less": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/less/-/less-4.1.3.tgz", + "integrity": "sha512-w16Xk/Ta9Hhyei0Gpz9m7VS8F28nieJaL/VyShID7cYvP6IL5oHeL6p4TXSDJqZE/lNv0oJ2pGVjJsRkfwm5FA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-11.1.0.tgz", + "integrity": "sha512-C+uDBV7kS7W5fJlUjq5mPBeBVhYpTIm5gB09APT9o3n/ILeaXVsiSFTbZpTJCJwQ/Crczfn3DmfQFwxYusWFug==", + "dev": true, + "dependencies": { + "klona": "^2.0.4" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/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, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/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==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/listr2": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", + "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", + "dev": true, + "dependencies": { + "cli-truncate": "^2.1.0", + "colorette": "^2.0.16", + "log-update": "^4.0.0", + "p-map": "^4.0.0", + "rfdc": "^1.3.0", + "rxjs": "^7.5.1", + "through": "^2.3.8", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/localtunnel": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/localtunnel/-/localtunnel-2.0.2.tgz", + "integrity": "sha512-n418Cn5ynvJd7m/N1d9WVJISLJF/ellZnfsLnx8WBWGzxv/ntNcFkJ1o6se5quUhCplfLGBNL5tYHiq5WF3Nug==", + "dev": true, + "dependencies": { + "axios": "0.21.4", + "debug": "4.3.2", + "openurl": "1.1.1", + "yargs": "17.1.1" + }, + "bin": { + "lt": "bin/lt.js" + }, + "engines": { + "node": ">=8.3.0" + } + }, + "node_modules/localtunnel/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/localtunnel/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, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/localtunnel/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/localtunnel/node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "dev": true, + "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": ">=12" + } + }, + "node_modules/localtunnel/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, + "engines": { + "node": ">=10" + } + }, + "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, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.escape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", + "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", + "dev": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true + }, + "node_modules/lodash.invokemap": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.invokemap/-/lodash.invokemap-4.6.0.tgz", + "integrity": "sha512-CfkycNtMqgUlfjfdh2BhKO/ZXrP8ePOX5lEU/g0R3ItJcnuxWDwokMGKx1hWcfOikmyOVx6X9IwWnDGlgKl61w==", + "dev": true + }, + "node_modules/lodash.isfinite": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", + "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "dev": true + }, + "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 + }, + "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 + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "dev": true + }, + "node_modules/lodash.pullall": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.pullall/-/lodash.pullall-4.2.0.tgz", + "integrity": "sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==", + "dev": true + }, + "node_modules/lodash.uniqby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz", + "integrity": "sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/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, + "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/log-symbols/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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 + }, + "node_modules/log-symbols/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-update": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", + "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.3.0", + "cli-cursor": "^3.1.0", + "slice-ansi": "^4.0.0", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-update/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 + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dev": true, + "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/logform/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==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/macos-release": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", + "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + }, + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "dev": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/make-fetch-happen/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/make-fetch-happen/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, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/rimraf/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, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/make-fetch-happen/node_modules/rimraf/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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/make-fetch-happen/node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", + "dev": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "dev": true, + "dependencies": { + "unique-slug": "^3.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "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==", + "dev": 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, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem-fs": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-4.0.0.tgz", + "integrity": "sha512-GCt8Xh5cRZ1ep45A8T+NlUYCPHzSj5JG2Xo7CA4Z8ISZ8Ovw0UQhQGt+J/z2xk7M3F9F4X9ZjSYk9UYlLLy4Wg==", + "dev": true, + "dependencies": { + "@types/node": "^20.8.3", + "@types/vinyl": "^2.0.8", + "vinyl": "^3.0.0", + "vinyl-file": "^5.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/mem-fs-editor": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-11.0.0.tgz", + "integrity": "sha512-D9+DPIDohRxYUO95vR3RxJZUNkCmehOHqIoJVm9OxChMadCsNdiTB1nuU4zaHJonUMASYdfKN2USxnB9z0ccXg==", + "dev": true, + "dependencies": { + "@types/ejs": "^3.1.3", + "@types/node": "^18.18.5", + "binaryextensions": "^4.18.0", + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^3.1.9", + "globby": "^13.2.2", + "isbinaryfile": "^5.0.0", + "minimatch": "^9.0.3", + "multimatch": "^6.0.0", + "normalize-path": "^3.0.0", + "textextensions": "^5.16.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "mem-fs": "^4.0.0" + } + }, + "node_modules/mem-fs-editor/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, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/mem-fs-editor/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mem-fs-editor/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mem-fs-editor/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mem-fs/node_modules/@types/node": { + "version": "20.10.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.4.tgz", + "integrity": "sha512-D08YG6rr8X90YB56tSIuBaddy/UXAA9RKJoFvrsnogAum/0pmjkgi4+2nx96A330FmioegBWmEYQ+syqCFaveg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/memfs": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", + "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", + "dev": true, + "dependencies": { + "fs-monkey": "^1.0.4" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "node_modules/merge-jsons-webpack-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/merge-jsons-webpack-plugin/-/merge-jsons-webpack-plugin-2.0.1.tgz", + "integrity": "sha512-8GP8rpOX3HSFsm7Gx+b3OAQR7yhgeAQvMqcZOJ+/cQIrqdak1c42a2T2vyeee8pzGPBf7pMLumthPh4CHgv2BA==", + "dev": true, + "dependencies": { + "glob": "7.1.1" + } + }, + "node_modules/merge-jsons-webpack-plugin/node_modules/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-mRyN/EsN2SyNhKWykF3eEGhDpeNplMWaW18Bmh76tnOqk5TbELAVwFAYOCmKVssOYFrYvvLMguiA+NXO3ZTuVA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "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 + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.7.6", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.7.6.tgz", + "integrity": "sha512-Qk7HcgaPkGG6eD77mLvZS1nmxlao3j+9PkrT9Uc7HAE1id3F41+DdBRYRYkbyfNRGzm8/YWtzhw7nVPmwhqTQw==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "dev": true, + "dependencies": { + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "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==", + "dev": true + }, + "node_modules/mitt": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-1.2.0.tgz", + "integrity": "sha512-r6lj77KlwqLhIUku9UWYes7KJtsczvolZkzp8hbaDPPaE24OmWl5s539Mytlj22siEQKosZ26qCBgda2PKwoJw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multimatch": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-6.0.0.tgz", + "integrity": "sha512-I7tSVxHGPlmPN/enE3mS1aOSo6bWBfls+3HmuEeCUBCE7gWnm3cBXCBkpurzFjVRwC6Kld8lLaZ1Iv5vOcjvcQ==", + "dev": true, + "dependencies": { + "@types/minimatch": "^3.0.5", + "array-differ": "^4.0.0", + "array-union": "^3.0.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/multimatch/node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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 + }, + "node_modules/needle": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.0.tgz", + "integrity": "sha512-Kaq820952NOrLY/LVbIhPZeXtCGDBAPVgT0BYnoT3p/Nr3nkGXdvWXXA3zgy7wpAgqRULu9p/NvKiFo6f/12fw==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/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==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/ngx-infinite-scroll": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-16.0.0.tgz", + "integrity": "sha512-bzyNYd+wVlUUxcopRVr2DAa81eEc8vITtKVvb+c7R1uy8hWPTlxOEXf3L1qA4FMwTEzCQ9b37TXzlJji3qBy+A==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/common": ">=16.0.0 <17.0.0", + "@angular/core": ">=16.0.0 <17.0.0" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "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, + "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-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true + }, + "node_modules/node-fetch/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==", + "dev": true + }, + "node_modules/node-fetch/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==", + "dev": true, + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", + "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.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": "^12.13 || ^14.13 || >=16" + } + }, + "node_modules/node-gyp-build": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.7.1.tgz", + "integrity": "sha512-wTSrZ+8lsRRa3I3H8Xr65dLWSgCvY2l4AOnaeKdPA9TB/WYMPaTcrzf3rXvFoVvjKNVnu0CcWSx54qq9GKRUYg==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp/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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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 + }, + "node_modules/node-notifier": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-9.0.1.tgz", + "integrity": "sha512-fPNFIp2hF/Dq7qLDzSg4vZ0J4e9v60gJR+Qx7RbjbWqzPDdEqeVpEx5CFeDAELIl+A/woaaNn1fQ5nEVerMxJg==", + "dev": true, + "dependencies": { + "growly": "^1.3.0", + "is-wsl": "^2.2.0", + "semver": "^7.3.2", + "shellwords": "^0.1.1", + "uuid": "^8.3.0", + "which": "^2.0.2" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-5.0.0.tgz", + "integrity": "sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.0.tgz", + "integrity": "sha512-Vq0eyEQy+elFpzsKjMss9kxqb9tG3YHg4dsyWuUENuzvSUWe1TCnW/vV9FkhvBk/brEDoDiVd+M1Btosa6ImdQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-10.1.0.tgz", + "integrity": "sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^6.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-7.0.4.tgz", + "integrity": "sha512-d6RGEuRrNS5/N84iglPivjaJPxhDbZmlbTwTDX2IbcRHG5bZCdtysYMhwiPvcF4GisXHGn7xsxv+GQ7T/02M5Q==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-8.0.1.tgz", + "integrity": "sha512-mRtvlBjTsJvfCCdmPtiu2bdlx8d/KXtF7yNXNWe7G0Z36qWA9Ny5zXsI2PfBZEv7SXgoxTmNaTzGSbbzDZChoA==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^10.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz", + "integrity": "sha512-kIDMIo4aBm6xg7jOttupWZamsZRkAqMqwqqbVXnUqstY5+tapvv6bkH/qMR76jdgV+YljEUCyWx3hRYMrJiAgA==", + "dev": true, + "dependencies": { + "make-fetch-happen": "^11.0.0", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^10.0.0", + "proc-log": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm-registry-fetch/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/npm-registry-fetch/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm-registry-fetch/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": 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/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.7.tgz", + "integrity": "sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==", + "dev": true + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-path": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.8.tgz", + "integrity": "sha512-YJjNZrlXJFM42wTBn6zgOJVar9KFJvzx6sTWDte8sWZF//cnjl0BxHNpfZx+ZffXX63A9q0b1zsFiBX4g4X5KA==", + "dev": true, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, + "node_modules/object.values": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dev": true, + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/onetime/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, + "engines": { + "node": ">=6" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/openurl": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/openurl/-/openurl-1.1.1.tgz", + "integrity": "sha512-d/gTkTb1i1GKz5k3XE3XFV/PxQ1k45zDqGP2OA7YhgsaLoqm6qRvARAZOFer1fcXritWlGBRCu/UgeS4HAnXAA==", + "dev": true + }, + "node_modules/opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/opn/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/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, + "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/ora/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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 + }, + "node_modules/ora/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/os-locale": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-6.0.2.tgz", + "integrity": "sha512-qIb8bzRqaN/vVqEYZ7lTAg6PonskO7xOmM7OClD28F6eFa4s5XGe4bGpHUHMoCHbNNuR0pDYFeSLiW5bnjWXIA==", + "dev": true, + "dependencies": { + "lcid": "^3.1.1" + }, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-name": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", + "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", + "dev": true, + "dependencies": { + "macos-release": "^2.5.0", + "windows-release": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ospath": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", + "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", + "dev": true + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "engines": { + "node": ">=12.20" + } + }, + "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, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "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", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", + "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==", + "dev": true, + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-transform": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/p-transform/-/p-transform-4.1.5.tgz", + "integrity": "sha512-CsXIiCOeBUYMBLpcY71DTq+fg8268ux31pAxI5TcoYEPfWCw5ozrbgWdZ9QmSDd8dUzvNXtmiwJOdTIxIFptfQ==", + "dev": true, + "dependencies": { + "@types/node": "^16.18.31", + "p-queue": "^7.3.0", + "readable-stream": "^4.3.0" + }, + "engines": { + "node": ">=16.13.0" + } + }, + "node_modules/p-transform/node_modules/@types/node": { + "version": "16.18.68", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.68.tgz", + "integrity": "sha512-sG3hPIQwJLoewrN7cr0dwEy+yF5nD4D/4FxtQpFciRD/xwUzgD+G05uxZHv5mhfXo4F9Jkp13jjn0CC2q325sg==", + "dev": true + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", + "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", + "dev": true, + "dependencies": { + "got": "^12.1.0", + "registry-auth-token": "^5.0.1", + "registry-url": "^6.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pacote": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-15.2.0.tgz", + "integrity": "sha512-rJVZeIwHTUta23sIZgEIM62WYwbmGbThdbnkt81ravBplQv+HjyroqnLRNH2+sLJHcGZmLRmhPwACqhfTcOmnA==", + "dev": true, + "dependencies": { + "@npmcli/git": "^4.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/promise-spawn": "^6.0.1", + "@npmcli/run-script": "^6.0.0", + "cacache": "^17.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^5.0.0", + "npm-package-arg": "^10.0.0", + "npm-packlist": "^7.0.0", + "npm-pick-manifest": "^8.0.0", + "npm-registry-fetch": "^14.0.0", + "proc-log": "^3.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^6.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^1.3.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/pacote/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-conflict-json": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", + "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-conflict-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/parse-gitignore": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz", + "integrity": "sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "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==", + "dev": true, + "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/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?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==", + "dev": true, + "engines": { + "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, + "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", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "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, + "engines": { + "node": ">=8" + } + }, + "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 + }, + "node_modules/path-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dev": true, + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">= 6" + } + }, + "node_modules/piscina": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.0.0.tgz", + "integrity": "sha512-641nAmJS4k4iqpNUqfggqUBUMmlw0ZoM5VZKdQkV2e970Inn3Tk9kroCc1wpsYLD07vCwpys5iY0d3xI/9WkTg==", + "dev": true, + "dependencies": { + "eventemitter-asyncresource": "^1.0.0", + "hdr-histogram-js": "^2.0.1", + "hdr-histogram-percentiles-obj": "^3.0.0" + }, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz", + "integrity": "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-up/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/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, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-up/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/pkg-up/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pluralize": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", + "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/portscanner": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", + "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", + "dev": true, + "dependencies": { + "async": "^2.6.0", + "is-number-like": "^1.0.3" + }, + "engines": { + "node": ">=0.4", + "npm": ">=1.0.0" + } + }, + "node_modules/portscanner/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.3.tgz", + "integrity": "sha512-YgO/yhtevGO/vJePCQmTxiaEwER94LABZN0ZMT4A0vsak9TpO+RvKRs7EmJ8peIlB9xfXCsS7M8LjqncsUZ5HA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.2.0", + "jiti": "^1.18.2", + "semver": "^7.3.8" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.3.tgz", + "integrity": "sha512-2/u2zraspoACtrbFRnTijMiQtb4GW4BvatjaG/bCjYQo8kLTdevCUlwuBHx2sCnSyrI3x3qj4ZK1j5LQBgzmwA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz", + "integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-java": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-java/-/prettier-plugin-java-2.3.1.tgz", + "integrity": "sha512-OYn8skqKnE5YUVL8f2ocayA6XCJK8PqsEz3pfATbDqzgdaSYDLhE/s8KrXrX9gj8KXIG6Wx0CMoXTNH8+ED22w==", + "dev": true, + "dependencies": { + "java-parser": "2.0.5", + "lodash": "4.17.21", + "prettier": "3.0.3" + } + }, + "node_modules/prettier-plugin-java/node_modules/chevrotain": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-6.5.0.tgz", + "integrity": "sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==", + "dev": true, + "dependencies": { + "regexp-to-ast": "0.4.0" + } + }, + "node_modules/prettier-plugin-java/node_modules/java-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/java-parser/-/java-parser-2.0.5.tgz", + "integrity": "sha512-AwieTO24Itcu0GgP9pBXs8gkqBtkmReclpBgXF4NkbIjdS7cn7hqpebjTmb5ouYYLFR+m3yh5fR3nW1NRrthdg==", + "dev": true, + "dependencies": { + "chevrotain": "6.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/prettier-plugin-packagejson": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.4.6.tgz", + "integrity": "sha512-5JGfzkJRL0DLNyhwmiAV9mV0hZLHDwddFCs2lc9CNxOChpoWUQVe8K4qTMktmevmDlMpok2uT10nvHUyU59sNw==", + "dev": true, + "dependencies": { + "sort-package-json": "2.6.0", + "synckit": "0.8.5" + }, + "peerDependencies": { + "prettier": ">= 1.16.0" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-properties": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-properties/-/prettier-plugin-properties-0.3.0.tgz", + "integrity": "sha512-j2h4NbG6hIaRx0W7CDPUH+yDbzXHmI2urPC1EC8pYrsS5R5AYgAmcSDN0oea+C5mBiN6BK0AkiSKPj0RC17NDQ==", + "dev": true, + "dependencies": { + "dot-properties": "^1.0.0" + }, + "peerDependencies": { + "prettier": ">= 2.3.0" + } + }, + "node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "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 + }, + "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, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-all-reject-late": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", + "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/promise-call-limit": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.2.tgz", + "integrity": "sha512-1vTUnfI2hzui8AEIixbdAJlFY4LFDXqQswy/2eOlThAscXCY4It8FdVuI0fMJGAB2aWGbdQf/gv0skKYXmdrHA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": 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==", + "dev": 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, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "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==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", + "dev": true + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/qs": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", + "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "dev": true, + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "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==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "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==", + "dev": true, + "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/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", + "integrity": "sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==", + "dev": true, + "dependencies": { + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", + "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", + "dev": true, + "dependencies": { + "json-parse-even-better-errors": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-package-json/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-pkg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-8.1.0.tgz", + "integrity": "sha512-PORM8AgzXeskHO/WEv312k9U03B8K9JSiWF/8N9sUuFjBa+9SF2u6K7VClzXwDXab51jCd8Nd36CNM+zR97ScQ==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^6.0.0", + "parse-json": "^7.0.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-10.1.0.tgz", + "integrity": "sha512-aNtBq4jR8NawpKJQldrQcSW9y/d+KWH4v24HWkHljOZ7H0av+YTGANBzRh9A5pw7v/bLVsLVPpOhJ7gHNVy8lA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0", + "read-pkg": "^8.1.0", + "type-fest": "^4.2.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg-up/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/read-pkg-up/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/hosted-git-info": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.1.tgz", + "integrity": "sha512-+K84LB1DYwMHoHSgaOY/Jfhw3ucPmSET5v98Ke/HdNSw4a0UktWzyW1mjhjpuxxTqOOsfWT/7iVshHmVZ4IpOA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/read-pkg/node_modules/json-parse-even-better-errors": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.1.tgz", + "integrity": "sha512-aatBvbL26wVUCLmbWdCpeu9iF5wOyWpagiKkInA+kfws3sWdBrTnsvN2CKcyCYyUrc7rebNBlK6+kteg7ksecg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-pkg/node_modules/lines-and-columns": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", + "integrity": "sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/read-pkg/node_modules/lru-cache": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz", + "integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/read-pkg/node_modules/normalize-package-data": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.0.tgz", + "integrity": "sha512-UL7ELRVxYBHBgYEtZCXjxuD5vPxnmvMGq0jp/dGPKKrN7tfsBh2IY7TlJ15WWwdjRWD3RJbnsygUurTK3xkPkg==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/read-pkg/node_modules/parse-json": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", + "integrity": "sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.21.4", + "error-ex": "^1.3.2", + "json-parse-even-better-errors": "^3.0.0", + "lines-and-columns": "^2.0.3", + "type-fest": "^3.8.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-pkg/node_modules/parse-json/node_modules/type-fest": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-3.13.1.tgz", + "integrity": "sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==" + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.2.11", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.2.11.tgz", + "integrity": "sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==", + "dev": true + }, + "node_modules/regexp-to-ast": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.4.0.tgz", + "integrity": "sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==", + "dev": true + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/registry-auth-token": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.0.2.tgz", + "integrity": "sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==", + "dev": true, + "dependencies": { + "@pnpm/npm-conf": "^2.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/registry-url": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", + "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", + "dev": true, + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true + }, + "node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dev": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-progress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", + "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", + "dev": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "dev": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "dev": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "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==", + "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", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.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/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true + }, + "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, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/resp-modifier": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", + "integrity": "sha512-U1+0kWC/+4ncRFYqQWTx/3qkfE6a4B/h3XXgmXypfa0SPZ3t7cbbaFk297PjQS/yov24R18h6OZe6iZwj3NSLw==", + "dev": true, + "dependencies": { + "debug": "^2.2.0", + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/resp-modifier/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/resp-modifier/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "engines": { + "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==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz", + "integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz", + "integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "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/run-applescript/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-applescript/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, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rx": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", + "integrity": "sha512-CiaiuN6gapkdl+cZUr67W6I8jquN4lkak3vtIsIWCl4XIPP8ffsoyN6/+PuGXnQy8Cu8W2y9Xxh31Rq4M6wUug==", + "dev": true + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/sass": { + "version": "1.64.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", + "integrity": "sha512-16rRACSOFEE8VN7SCgBu1MpYCyN7urj9At898tyzdXFhC+a+yOX5dXwAR7L8/IdPJ1NB8OYoXmD55DM30B2kEQ==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-13.3.2.tgz", + "integrity": "sha512-CQbKl57kdEv+KDLquhC+gE3pXt74LEAzm+tzywcA0/aHZuub8wTErbjAoNI57rPUWRYRNC5WUnNl8eGJNbDdwg==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "fibers": ">= 3.1.0", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "fibers": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + } + } + }, + "node_modules/sass/node_modules/immutable": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", + "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "dev": true + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true, + "optional": true + }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, + "bin": { + "mime": "cli.js" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/server-destroy": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/server-destroy/-/server-destroy-1.0.1.tgz", + "integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==", + "dev": true + }, + "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==", + "dev": true + }, + "node_modules/set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "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==", + "dev": true + }, + "node_modules/sigstore": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-1.9.0.tgz", + "integrity": "sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^1.1.0", + "@sigstore/protobuf-specs": "^0.2.0", + "@sigstore/sign": "^1.0.0", + "@sigstore/tuf": "^1.0.3", + "make-fetch-happen": "^11.0.1" + }, + "bin": { + "sigstore": "bin/sigstore.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sigstore/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sigstore/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/sigstore/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/sigstore/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sigstore/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/sigstore/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/simple-git": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.20.0.tgz", + "integrity": "sha512-ozK8tl2hvLts8ijTs18iFruE+RoqmC/mqZhjs/+V7gS5W68JpJ3+FCTmLVqmR59MaUQ52MfGQuWsIqfsTbbJ0Q==", + "dev": true, + "dependencies": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.3.4" + }, + "funding": { + "type": "github", + "url": "https://github.com/steveukx/git-js?sponsor=1" + } + }, + "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==", + "dev": true, + "dependencies": { + "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==", + "dev": true + }, + "node_modules/sirv": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.3.tgz", + "integrity": "sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^3.0.0" + }, + "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 + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/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 + }, + "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==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", + "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "dev": true, + "dependencies": { + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "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/socket.io-client": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.2.tgz", + "integrity": "sha512-vtA0uD4ibrYD793SOIAwlo8cj6haOeMHrGvwPxJsxH7CeIksqJ+3Zc06RvWTIFgiSqx4A3sOnTXpfAEE2Zyz6w==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dev": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sort-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-5.0.0.tgz", + "integrity": "sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==", + "dev": true, + "dependencies": { + "is-plain-obj": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-object-keys": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sort-object-keys/-/sort-object-keys-1.1.3.tgz", + "integrity": "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==", + "dev": true + }, + "node_modules/sort-package-json": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/sort-package-json/-/sort-package-json-2.6.0.tgz", + "integrity": "sha512-XSQ+lY9bAYA8ZsoChcEoPlgcSMaheziEp1beox1JVxy1SV4F2jSq9+h2rJ+3mC/Dhu9Ius1DLnInD5AWcsDXZw==", + "dev": true, + "dependencies": { + "detect-indent": "^7.0.1", + "detect-newline": "^4.0.0", + "get-stdin": "^9.0.0", + "git-hooks-list": "^3.0.0", + "globby": "^13.1.2", + "is-plain-obj": "^4.1.0", + "sort-object-keys": "^1.1.3" + }, + "bin": { + "sort-package-json": "cli.js" + } + }, + "node_modules/sort-package-json/node_modules/detect-newline": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-4.0.1.tgz", + "integrity": "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-package-json/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-package-json/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-4.0.1.tgz", + "integrity": "sha512-oqXpzDIByKONVY8g1NUPOTQhe0UTU5bWUl32GSkqK2LjJj0HmwTMVKxcUip0RgAYhY1mqgOxjbQM48a0mmeNfA==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/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==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "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, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "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, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "dev": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ssri": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.5.tgz", + "integrity": "sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/stack-trace": { + "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==", + "dev": true, + "engines": { + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha512-wuTCPGlJONk/a1kqZ4fQM2+908lC7fa7nPYpTC1EhnvqLX/IICbeP1OZGDtA374trpSq68YubKUMo8oRhN46yg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stdin-discarder/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/stdin-discarder/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/stream-throttle": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz", + "integrity": "sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==", + "dev": true, + "dependencies": { + "commander": "^2.2.0", + "limiter": "^1.0.5" + }, + "bin": { + "throttleproxy": "bin/throttleproxy.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/stream-throttle/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/streamx": { + "version": "2.15.6", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.6.tgz", + "integrity": "sha512-q+vQL4AAz+FdfT137VF69Cc/APqUbxy+MDOImRrMvchJpigHj9GksgDU2LYbO9rx7RX6osWgxJB2WxhYv4SZAw==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "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, + "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", + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "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==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom-buf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-3.0.1.tgz", + "integrity": "sha512-iJaWw2WroigLHzQysdc5WWeUc99p7ea7AEgB6JkY8CMyiO1yTVAA1gIlJJgORElUIR+lcZJkNl1OGChMhvc2Cw==", + "dev": true, + "dependencies": { + "is-utf8": "^0.2.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-bom-stream": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-5.0.0.tgz", + "integrity": "sha512-Yo472mU+3smhzqeKlIxClre4s4pwtYZEvDNQvY/sJpnChdaxmKuwU28UVx/v1ORKNMxkmj1GBuvxJQyBk6wYMQ==", + "dev": true, + "dependencies": { + "first-chunk-stream": "^5.0.0", + "strip-bom-buf": "^3.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, + "node_modules/stubborn-fs": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-1.2.5.tgz", + "integrity": "sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==", + "dev": true + }, + "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==", + "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, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.9.1", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.9.1.tgz", + "integrity": "sha512-5zAx+hUwJb9T3EAntc7TqYkV716CMqG6sZpNlAAMOMWkNXRYxGkN8ADIvD55dQZ10LxN90ZM/TQmN7y1gpICnw==", + "dev": true + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synckit": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", + "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", + "dev": true, + "dependencies": { + "@pkgr/utils": "^2.3.1", + "tslib": "^2.5.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", + "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "dev": true, + "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/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==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/terser": { + "version": "5.19.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.2.tgz", + "integrity": "sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/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, + "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/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-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, + "engines": { + "node": ">=8" + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/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 + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser-webpack-plugin/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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "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, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/textextensions": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-5.16.0.tgz", + "integrity": "sha512-7D/r3s6uPZyU//MCYrX6I14nzauDwJ5CxazouuRGNuvSCihW87ufN6VLoROLCrHg6FblLuJrT6N2BVaPVzqElw==", + "dev": true, + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/titleize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz", + "integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tmp/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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 + }, + "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==", + "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", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "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", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/tr46": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", + "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/treeverse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", + "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/triple-beam": { + "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==", + "dev": true, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.1.tgz", + "integrity": "sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "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, + "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/ts-node/node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ts-node/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, + "engines": { + "node": ">=0.3.1" + } + }, + "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, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-1.1.7.tgz", + "integrity": "sha512-i3P9Kgw3ytjELUfpuKVDNBJvk4u5bXL6gskv572mcevPbSKCV3zt3djhmlEQ65yERjIbOSncy7U4cQJaB1CBCg==", + "dev": true, + "dependencies": { + "@tufjs/models": "1.0.4", + "debug": "^4.3.4", + "make-fetch-happen": "^11.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/tuf-js/node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tuf-js/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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/tuf-js/node_modules/make-fetch-happen": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-11.1.1.tgz", + "integrity": "sha512-rLWS7GCSTcEujjVBs2YqG7Y4643u8ucvCJeSRqiLYhesrDuzeuFIk37xREzAsfQaqzl8b9rNCE4m6J8tvX4Q8w==", + "dev": true, + "dependencies": { + "agentkeepalive": "^4.2.1", + "cacache": "^17.0.0", + "http-cache-semantics": "^4.1.1", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^5.0.0", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/tuf-js/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tuf-js/node_modules/minipass-fetch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.4.tgz", + "integrity": "sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/tuf-js/node_modules/minipass-fetch/node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "dev": true, + "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", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "dev": true + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "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, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.6.0.tgz", + "integrity": "sha512-rLjWJzQFOq4xw7MgJrCZ6T1jIOvvYElXT12r+y0CC6u67hegDHaxcPqb2fZHOGlqxugGQPNB1EnTezjBetkwkw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "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", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "1.0.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", + "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/uint8array-extras": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-0.3.0.tgz", + "integrity": "sha512-erJsJwQ0tKdwuqI0359U8ijkFmfiTcq25JvvzRVc1VP+2son1NJRXhxcAKJmAW3ajM8JSGAfsAXye8g4s+znxA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universal-user-agent": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz", + "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==", + "dev": true + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.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==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/untildify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", + "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "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" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "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", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "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", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "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/v8-to-istanbul/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 + }, + "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, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.0.tgz", + "integrity": "sha512-YuKoXDAhBYxY7SfOKxHBDoSyENFeW5VvIIQp2TGQuit8gpK6MnWaQelBKxso72DoxTZfZdcP3W90LqpSkgPzLQ==", + "dev": true, + "dependencies": { + "builtins": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/vinyl": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.0.tgz", + "integrity": "sha512-rC2VRfAVVCGEgjnxHUnpIVh3AGuk62rP3tqVrn+yab0YH7UULisC085+NYH+mnqf3Wx4SpSi1RQMwudL89N03g==", + "dev": true, + "dependencies": { + "clone": "^2.1.2", + "clone-stats": "^1.0.0", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-file": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-5.0.0.tgz", + "integrity": "sha512-MvkPF/yA1EX7c6p+juVIvp9+Lxp70YUfNKzEWeHMKpUNVSnTZh2coaOqLxI0pmOe2V9nB+OkgFaMDkodaJUyGw==", + "dev": true, + "dependencies": { + "@types/vinyl": "^2.0.7", + "strip-bom-buf": "^3.0.1", + "strip-bom-stream": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/vite": { + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.7.tgz", + "integrity": "sha512-6pYf9QJ1mHylfVh39HpuSfMPojPSKVxZvnclX1K1FyZ1PXDOcLBibdq5t1qxJSnL63ca8Wf4zts6mD8u8oc9Fw==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.26", + "rollup": "^3.25.2" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.11.tgz", + "integrity": "sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "dev": true + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", + "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", + "dev": true, + "dependencies": { + "xml-name-validator": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/wait-on": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.0.1.tgz", + "integrity": "sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==", + "dev": true, + "dependencies": { + "axios": "^0.27.2", + "joi": "^17.7.0", + "lodash": "^4.17.21", + "minimist": "^1.2.7", + "rxjs": "^7.8.0" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/wait-on/node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/wait-on/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/walk-up-path": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", + "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", + "dev": true + }, + "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, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dev": true, + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/webidl-conversions": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", + "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", + "dev": true, + "engines": { + "node": ">=10.4" + } + }, + "node_modules/webpack": { + "version": "5.89.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.89.0.tgz", + "integrity": "sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz", + "integrity": "sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "is-plain-object": "^5.0.0", + "lodash.debounce": "^4.0.8", + "lodash.escape": "^4.0.1", + "lodash.flatten": "^4.4.0", + "lodash.invokemap": "^4.6.0", + "lodash.pullall": "^4.2.0", + "lodash.uniqby": "^4.7.0", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/acorn-walk": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz", + "integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.1.tgz", + "integrity": "sha512-y51HrHaFeeWir0YO4f0g+9GwZawuigzcAdRNon6jErXy/SqV/+O6eaVAzDqE6t3e3NpGeR5CS+cCDaTC+V3yEQ==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.12", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", + "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.9", + "@types/connect-history-api-fallback": "^1.3.5", + "@types/express": "^4.17.13", + "@types/serve-index": "^1.9.1", + "@types/serve-static": "^1.13.10", + "@types/sockjs": "^0.3.33", + "@types/ws": "^8.5.5", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.0.11", + "chokidar": "^3.5.3", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.3.2", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.0.1", + "launch-editor": "^2.6.0", + "open": "^8.0.9", + "p-retry": "^4.5.0", + "rimraf": "^3.0.2", + "schema-utils": "^4.0.0", + "selfsigned": "^2.1.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^5.3.1", + "ws": "^8.13.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.37.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", + "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^3.4.3", + "mime-types": "^2.1.31", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.15.0.tgz", + "integrity": "sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw==", + "dev": true, + "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/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-notifier": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/webpack-notifier/-/webpack-notifier-1.15.0.tgz", + "integrity": "sha512-N2V8UMgRB5komdXQRavBsRpw0hPhJq2/SWNOGuhrXpIgRhcMexzkGQysUyGStHLV5hkUlgpRiF7IUXoBqyMmzQ==", + "dev": true, + "dependencies": { + "node-notifier": "^9.0.0", + "strip-ansi": "^6.0.0" + }, + "peerDependencies": { + "@types/webpack": ">4.41.31" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + } + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/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, + "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/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/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 + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "node_modules/whatwg-url": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz", + "integrity": "sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==", + "dev": true, + "dependencies": { + "lodash": "^4.7.0", + "tr46": "^2.1.0", + "webidl-conversions": "^6.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/when-exit": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.1.tgz", + "integrity": "sha512-XLipGldz/UcleuGaoQjbYuWwD+ICRnzIjlldtwTaTWr7aZz8yQW49rXk6MHQnh+KxOiWiJpM1vIyaxprOnlW4g==", + "dev": true + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-package-manager": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/which-package-manager/-/which-package-manager-0.0.1.tgz", + "integrity": "sha512-a+bCExXd8OdYky5J59nimHxTCRPhxZSQtwKh3Ew6lpC4oY9f3KH77XDxcPrComVhSEPtvMjZigS2vZgZfgJuxA==", + "dev": true, + "dependencies": { + "execa": "^7.1.1", + "find-up": "^6.3.0", + "micromatch": "^4.0.5" + }, + "engines": { + "node": "^16.13.0 || >=18.12.0" + } + }, + "node_modules/which-package-manager/node_modules/execa": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz", + "integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.1", + "human-signals": "^4.3.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^3.0.7", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": "^14.18.0 || ^16.14.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/which-package-manager/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/human-signals": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz", + "integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==", + "dev": true, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/which-package-manager/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/which-package-manager/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-package-manager/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/windows-release": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", + "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", + "dev": true, + "dependencies": { + "execa": "^4.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/winston": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", + "integrity": "sha512-L3yR6/MzZAOl0DsysUXHVjOwv8mKZ71TrA/41EIduGpOOV5LQVodqN+QdQ6BS6PJ/RdIshZhq84P/fStEZkk7g==", + "dev": true, + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.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.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.6.0.tgz", + "integrity": "sha512-wbBA9PbPAHxKiygo7ub7BYRiKxms0tpfU2ljtWzb3SjRjv5yl6Ozuy/TkXf00HTAt+Uylo3gSkNwzc4ME0wiIg==", + "dev": true, + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/winston/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==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/winston/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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/wrap-ansi-cjs": { + "name": "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, + "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/wrap-ansi-cjs/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, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/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 + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/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==" + }, + "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==", + "dev": true + }, + "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, + "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": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", + "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "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==", + "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==", + "dev": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yeoman-environment": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-4.0.0.tgz", + "integrity": "sha512-pdr9XhhIfRQmjG+VO2J0420SeI/E5O+dM8CLIG1+8q01kqtiZoxgfI1WGeMiZMIVv4jr8w63qhHVYr7gpRLDVA==", + "dev": true, + "dependencies": { + "@yeoman/adapter": "^1.4.0", + "@yeoman/conflicter": "^2.0.0-alpha.2", + "@yeoman/namespace": "^1.0.0", + "@yeoman/transform": "^1.2.0", + "arrify": "^3.0.0", + "chalk": "^5.3.0", + "commander": "^11.1.0", + "debug": "^4.3.4", + "execa": "^8.0.1", + "fly-import": "^0.4.0", + "globby": "^13.2.2", + "grouped-queue": "^2.0.0", + "locate-path": "^7.2.0", + "lodash-es": "^4.17.21", + "mem-fs-editor": "^11.0.0", + "semver": "^7.5.4", + "slash": "^5.1.0", + "untildify": "^5.0.0", + "which-package-manager": "^0.0.1" + }, + "bin": { + "yoe": "bin/bin.cjs" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "peerDependencies": { + "@yeoman/types": "^1.1.1", + "mem-fs": "^4.0.0" + } + }, + "node_modules/yeoman-environment/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/yeoman-environment/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/yeoman-environment/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/yeoman-environment/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/globby/node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/yeoman-environment/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/yeoman-environment/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-environment/node_modules/untildify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-5.0.0.tgz", + "integrity": "sha512-bOgQLUnd2G5rhzaTvh1VCI9Fo6bC5cLTpH17T5aFfamyXFYDbbdzN6IXdeoc3jBS7T9hNTmJtYUzJCJ2Xlc9gA==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/yeoman-environment/node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-7.1.0.tgz", + "integrity": "sha512-nRhHJN2CkfXLyqOQJTjLuZh0an3eYh8aspqxdr3UiBKSvi7uE0MzlR+6BoXwMAECSRoHkZq+jmhr5MdsUWTWXg==", + "dev": true, + "dependencies": { + "@types/lodash-es": "^4.17.9", + "@types/node": "^18.18.5", + "@yeoman/namespace": "^1.0.0", + "chalk": "^5.3.0", + "debug": "^4.1.1", + "execa": "^8.0.1", + "github-username": "^7.0.0", + "json-schema": "^0.4.0", + "latest-version": "^7.0.0", + "lodash-es": "^4.17.21", + "mem-fs-editor": "^11.0.0", + "minimist": "^1.2.8", + "read-pkg-up": "^10.1.0", + "semver": "^7.5.4", + "simple-git": "^3.20.0", + "sort-keys": "^5.0.0", + "text-table": "^0.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "peerDependencies": { + "@yeoman/types": "^1.1.1", + "mem-fs": "^4.0.0", + "yeoman-environment": "^4.0.0" + }, + "peerDependenciesMeta": { + "yeoman-environment": { + "optional": true + } + } + }, + "node_modules/yeoman-generator/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/yeoman-generator/node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/yeoman-generator/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator/node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/yeoman-generator/node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator/node_modules/npm-run-path": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz", + "integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator/node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yeoman-generator/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, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/yeoman-generator/node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.13.3.tgz", + "integrity": "sha512-MKPbmZie6fASC/ps4dkmIhaT5eonHkEt6eAy80K42tAm0G2W+AahLJjbfi6X9NPdciOE9GRFTTM8u2IiF6O3ww==", + "dependencies": { + "tslib": "^2.3.0" + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package.json new file mode 100644 index 0000000..49c6b3a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/package.json @@ -0,0 +1,153 @@ +{ + "name": "app", + "version": "0.0.1-SNAPSHOT", + "private": true, + "description": "Description for App", + "license": "UNLICENSED", + "scripts": { + "app:start": "./mvnw", + "app:up": "docker compose -f src/main/docker/app.yml up --wait", + "backend:build-cache": "./mvnw dependency:go-offline", + "backend:debug": "./mvnw -Dspring-boot.run.jvmArguments=\"-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000\"", + "backend:doc:test": "./mvnw -ntp javadoc:javadoc --batch-mode", + "backend:info": "./mvnw -ntp enforcer:display-info --batch-mode", + "backend:nohttp:test": "./mvnw -ntp checkstyle:check --batch-mode", + "backend:start": "./mvnw -Dskip.installnodenpm -Dskip.npm", + "backend:unit:test": "./mvnw -ntp -Dskip.installnodenpm -Dskip.npm verify --batch-mode -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.de.novatec.ittage.app=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF", + "build": "npm run webapp:prod --", + "build-watch": "concurrently 'npm run webapp:build:dev -- --watch' npm:backend:start", + "ci:backend:test": "npm run backend:info && npm run backend:doc:test && npm run backend:nohttp:test && npm run backend:unit:test -- -P$npm_package_config_default_environment", + "ci:e2e:package": "npm run java:$npm_package_config_packaging:$npm_package_config_default_environment -- -Pe2e -Denforcer.skip=true", + "ci:e2e:prepare": "npm run ci:e2e:prepare:docker", + "ci:e2e:prepare:docker": "npm run services:up --if-present && docker ps -a", + "ci:e2e:run": "concurrently -k -s first \"npm run ci:e2e:server:start\" \"npm run e2e:headless\"", + "preci:e2e:server:start": "npm run services:db:await --if-present && npm run services:others:await --if-present", + "ci:e2e:server:start": "java -jar target/e2e.$npm_package_config_packaging --spring.profiles.active=e2e,$npm_package_config_default_environment -Dlogging.level.ROOT=OFF -Dlogging.level.tech.jhipster=OFF -Dlogging.level.de.novatec.ittage.app=OFF -Dlogging.level.org.springframework=OFF -Dlogging.level.org.springframework.web=OFF -Dlogging.level.org.springframework.security=OFF --logging.level.org.springframework.web=ERROR", + "ci:e2e:teardown": "npm run ci:e2e:teardown:docker --if-present", + "ci:e2e:teardown:docker": "docker compose -f src/main/docker/services.yml down -v && docker ps -a", + "ci:frontend:build": "npm run webapp:build:$npm_package_config_default_environment", + "ci:frontend:test": "npm run ci:frontend:build && npm test", + "ci:server:await": "echo \"Waiting for server at port $npm_package_config_backend_port to start\" && wait-on -t 180000 http-get://127.0.0.1:$npm_package_config_backend_port/management/health && echo \"Server at port $npm_package_config_backend_port started\"", + "clean-www": "rimraf target/classes/static/", + "cleanup": "rimraf target/", + "cypress": "cypress open --e2e", + "docker:db:down": "docker compose -f src/main/docker/mysql.yml down -v", + "docker:db:up": "docker compose -f src/main/docker/mysql.yml up --wait", + "e2e": "npm run e2e:cypress:headed --", + "e2e:cypress": "cypress run --e2e --browser chrome", + "e2e:cypress:headed": "npm run e2e:cypress -- --headed", + "e2e:cypress:record": "npm run e2e:cypress -- --record", + "e2e:dev": "concurrently -k -s first \"./mvnw\" \"npm run e2e\"", + "e2e:devserver": "concurrently -k -s first \"npm run backend:start\" \"npm start\" \"wait-on -t 180000 http-get://127.0.0.1:9000 && npm run e2e:headless -- -c baseUrl=http://localhost:9000\"", + "pree2e:headless": "npm run ci:server:await", + "e2e:headless": "npm run e2e:cypress --", + "java:docker": "./mvnw -ntp verify -DskipTests -Pprod jib:dockerBuild", + "java:docker:arm64": "npm run java:docker -- -Djib-maven-plugin.architecture=arm64", + "java:docker:dev": "npm run java:docker -- -Pdev,webapp", + "java:docker:prod": "npm run java:docker -- -Pprod", + "java:jar": "./mvnw -ntp verify -DskipTests --batch-mode", + "java:jar:dev": "npm run java:jar -- -Pdev,webapp", + "java:jar:prod": "npm run java:jar -- -Pprod", + "java:war": "./mvnw -ntp verify -DskipTests --batch-mode -Pwar", + "java:war:dev": "npm run java:war -- -Pdev,webapp", + "java:war:prod": "npm run java:war -- -Pprod", + "jest": "jest --coverage --logHeapUsage --maxWorkers=2 --config jest.conf.js", + "lint": "eslint . --ext .js,.ts", + "lint:fix": "npm run lint -- --fix", + "prettier:check": "prettier --check \"{,src/**/,webpack/,.blueprint/**/}*.{md,json,yml,html,cjs,mjs,js,ts,tsx,css,scss,java}\"", + "prettier:format": "prettier --write \"{,src/**/,webpack/,.blueprint/**/}*.{md,json,yml,html,cjs,mjs,js,ts,tsx,css,scss,java}\"", + "serve": "npm run start --", + "services:up": "docker compose -f src/main/docker/services.yml up --wait", + "start": "ng serve --hmr", + "start-tls": "npm run webapp:dev-ssl", + "pretest": "npm run lint", + "test": "ng test --coverage --log-heap-usage -w=2", + "test:watch": "npm run test -- --watch", + "watch": "concurrently npm:start npm:backend:start", + "webapp:build": "npm run clean-www && npm run webapp:build:dev", + "webapp:build:dev": "ng build --configuration development", + "webapp:build:prod": "ng build --configuration production", + "webapp:dev": "ng serve", + "webapp:dev-ssl": "ng serve --ssl", + "webapp:dev-verbose": "ng serve --verbose", + "webapp:prod": "npm run clean-www && npm run webapp:build:prod", + "webapp:test": "npm run test --" + }, + "config": { + "backend_port": 8080, + "default_environment": "prod", + "packaging": "jar" + }, + "dependencies": { + "@angular/common": "16.2.11", + "@angular/compiler": "16.2.11", + "@angular/core": "16.2.11", + "@angular/forms": "16.2.11", + "@angular/localize": "16.2.11", + "@angular/platform-browser": "16.2.11", + "@angular/platform-browser-dynamic": "16.2.11", + "@angular/router": "16.2.11", + "@fortawesome/angular-fontawesome": "0.13.0", + "@fortawesome/fontawesome-svg-core": "6.4.2", + "@fortawesome/free-solid-svg-icons": "6.4.2", + "@ng-bootstrap/ng-bootstrap": "15.1.2", + "@ngx-translate/core": "15.0.0", + "@ngx-translate/http-loader": "8.0.0", + "@popperjs/core": "2.11.8", + "bootstrap": "5.3.2", + "dayjs": "1.11.10", + "ngx-infinite-scroll": "16.0.0", + "rxjs": "7.8.1", + "tslib": "2.6.2", + "zone.js": "0.13.3" + }, + "devDependencies": { + "@angular-builders/custom-webpack": "16.0.1", + "@angular-builders/jest": "16.0.1", + "@angular-devkit/build-angular": "16.2.9", + "@angular-eslint/eslint-plugin": "16.2.0", + "@angular/cli": "16.2.9", + "@angular/compiler-cli": "16.2.11", + "@angular/service-worker": "16.2.11", + "@types/jest": "29.5.7", + "@types/node": "18.18.8", + "@typescript-eslint/eslint-plugin": "6.9.1", + "@typescript-eslint/parser": "6.9.1", + "browser-sync": "2.29.3", + "browser-sync-webpack-plugin": "2.3.0", + "buffer": "6.0.3", + "concurrently": "8.2.2", + "copy-webpack-plugin": "11.0.0", + "cypress": "13.4.0", + "eslint": "8.52.0", + "eslint-config-prettier": "9.0.0", + "eslint-plugin-cypress": "2.15.1", + "eslint-webpack-plugin": "4.0.1", + "folder-hash": "4.0.4", + "generator-jhipster": "8.0.0", + "jest": "29.7.0", + "jest-date-mock": "1.0.8", + "jest-environment-jsdom": "29.7.0", + "jest-junit": "16.0.0", + "jest-preset-angular": "13.1.2", + "jest-sonar": "0.2.16", + "merge-jsons-webpack-plugin": "2.0.1", + "rimraf": "5.0.5", + "swagger-ui-dist": "5.9.1", + "ts-jest": "29.1.1", + "typescript": "5.1.6", + "wait-on": "7.0.1", + "webpack-bundle-analyzer": "4.9.1", + "webpack-merge": "5.10.0", + "webpack-notifier": "1.15.0" + }, + "engines": { + "node": ">=18.18.2" + }, + "cacheDirectories": [ + "node_modules" + ], + "overrides": { + "webpack": "5.89.0" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/pom.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/pom.xml new file mode 100644 index 0000000..c9ab7d0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/pom.xml @@ -0,0 +1,1208 @@ + + + 4.0.0 + + de.novatec.ittage.app + app + 0.0.1-SNAPSHOT + jar + App + Description for App + + + + 3.2.5 + 17 + v18.18.2 + 10.2.2 + UTF-8 + UTF-8 + yyyyMMddHHmmss + ${java.version} + ${java.version} + de.novatec.ittage.app.AppApp + -Djava.security.egd=file:/dev/./urandom -Xmx1G + jdt_apt + false + 8.0.0 + 3.1.5 + 1.1.0 + 10.12.4 + 1.11 + 1.14.2 + 4.6.0 + 3.9.5 + 7.0.0 + 6.2.13.Final + 0.8.11 + 4.0.4 + amd64 + eclipse-temurin:17-jre-focal + 3.4.0 + 1.0.0 + + + + 4.24.0 + 1.5.5.Final + 3.1.0 + 3.3.1 + 3.3.2 + 3.11.0 + 2.1 + 3.4.1 + 3.2.1 + 2.2.1 + 3.3.0 + 3.6.0 + 3.3.1 + 3.12.1 + 3.2.1 + 3.4.0 + 2.7.0 + 0.0.11 + + + + + + 1.2.1 + 3.10.0.2594 + 2.40.0 + 3.0.2 + + + + + + tech.jhipster + jhipster-dependencies + ${jhipster-dependencies.version} + pom + import + + + + + + + tech.jhipster + jhipster-framework + + + org.springframework.boot + spring-boot-configuration-processor + provided + + + org.springframework.boot + spring-boot-loader-tools + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-cache + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-logging + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-undertow + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-test + test + + + org.springframework.security + spring-security-data + + + org.springframework.security + spring-security-test + test + + + org.springdoc + springdoc-openapi-starter-webmvc-api + + + com.fasterxml.jackson.datatype + jackson-datatype-hibernate6 + + + com.fasterxml.jackson.datatype + jackson-datatype-hppc + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + + + com.fasterxml.jackson.module + jackson-module-jaxb-annotations + + + com.mysql + mysql-connector-j + + + com.tngtech.archunit + archunit-junit5-api + ${archunit-junit5.version} + test + + + + + com.tngtech.archunit + archunit-junit5-engine + ${archunit-junit5.version} + test + + + com.zaxxer + HikariCP + + + io.dropwizard.metrics + metrics-core + + + io.gatling.highcharts + gatling-charts-highcharts + ${gatling.version} + test + + + io.micrometer + micrometer-registry-prometheus + + + jakarta.annotation + jakarta.annotation-api + + + javax.cache + cache-api + + + org.apache.commons + commons-lang3 + + + org.ehcache + ehcache + jakarta + + + org.hibernate.orm + hibernate-core + ${hibernate.version} + + + org.hibernate.orm + hibernate-jcache + ${hibernate.version} + + + org.hibernate.orm + hibernate-jpamodelgen + provided + + + org.hibernate.validator + hibernate-validator + + + org.liquibase + liquibase-core + ${liquibase.version} + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + + + org.testcontainers + jdbc + test + + + org.testcontainers + mysql + test + + + + + spring-boot:run + + + org.springframework.boot + spring-boot-maven-plugin + + + com.diffplug.spotless + spotless-maven-plugin + + + com.google.cloud.tools + jib-maven-plugin + + + io.gatling + gatling-maven-plugin + + + org.apache.maven.plugins + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-eclipse-plugin + + + org.apache.maven.plugins + maven-enforcer-plugin + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.apache.maven.plugins + maven-idea-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + + org.apache.maven.plugins + maven-resources-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.codehaus.mojo + properties-maven-plugin + + + org.gaul + modernizer-maven-plugin + + + org.jacoco + jacoco-maven-plugin + + + org.sonarsource.scanner.maven + sonar-maven-plugin + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + repackage + + + + + ${start-class} + + + + + com.diffplug.spotless + spotless-maven-plugin + ${spotless-maven-plugin.version} + + + + + + + + spotless + process-sources + + apply + + + + + + com.github.eirslett + frontend-maven-plugin + ${frontend-maven-plugin.version} + + target + ${node.version} + ${npm.version} + + + + com.google.cloud.tools + jib-maven-plugin + ${jib-maven-plugin.version} + + + ${jib-maven-plugin.image} + + + ${jib-maven-plugin.architecture} + linux + + + + + app:latest + + + + bash + + /entrypoint.sh + + + 8080 + + + ALWAYS + 0 + + USE_CURRENT_TIMESTAMP + 1000 + + + src/main/docker/jib + + + /entrypoint.sh + 755 + + + + + + + io.gatling + gatling-maven-plugin + ${gatling-maven-plugin.version} + + true + ${project.basedir}/src/test/gatling/conf + + + + io.github.git-commit-id + git-commit-id-maven-plugin + ${git-commit-id-maven-plugin.version} + + + + revision + + + + + false + false + true + + ^git.commit.id.abbrev$ + ^git.commit.id.describe$ + ^git.branch$ + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + io.spring.nohttp + nohttp-checkstyle + ${nohttp-checkstyle.version} + + + + checkstyle.xml + pom.xml,README.md + .git/**/*,target/**/*,node_modules/**/*,node/**/* + ./ + + + + + check + + + + + + org.apache.maven.plugins + maven-clean-plugin + ${maven-clean-plugin.version} + + + org.apache.maven.plugins + maven-compiler-plugin + ${maven-compiler-plugin.version} + + ${java.version} + ${java.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${spring-boot.version} + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + + + org.hibernate.orm + hibernate-jpamodelgen + ${hibernate.version} + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + ${maven-eclipse-plugin.version} + + true + true + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + enforce-versions + + enforce + + + + enforce-dependencyConvergence + + + + + false + + + enforce + + + + + + + You are running an older version of Maven. JHipster requires at least Maven ${maven.version} + [${maven.version},) + + + You are running an incompatible version of Java. JHipster supports JDK 17 to 21. + [17,18),[18,19),[19,20),[20,21),[21,22) + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${maven-failsafe-plugin.version} + + + + ${project.build.outputDirectory} + alphabetical + + **/*IT* + **/*IntTest* + + @{argLine} -Dspring.profiles.active=${profile.test} + + + + integration-test + + integration-test + + + + verify + + verify + + + + + + org.apache.maven.plugins + maven-idea-plugin + ${maven-idea-plugin.version} + + node_modules + + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + ${maven.compiler.source} + + + + org.apache.maven.plugins + maven-resources-plugin + ${maven-resources-plugin.version} + + + config-resources + validate + + copy-resources + + + ${project.build.directory}/classes + false + + # + + + + src/main/resources/ + true + + config/*.yml + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + ${maven-site-plugin.version} + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + alphabetical + + **/*IT* + **/*IntTest* + + + + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + + + default-war + + war + + package + + + + WEB-INF/**,META-INF/** + false + target/classes/static/ + + + src/main/webapp + + WEB-INF/** + + + + + + + org.codehaus.mojo + properties-maven-plugin + ${properties-maven-plugin.version} + + + initialize + + read-project-properties + + + + sonar-project.properties + + + + + + + org.gaul + modernizer-maven-plugin + ${modernizer-maven-plugin.version} + + + modernizer + package + + modernizer + + + + + ${java.version} + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + pre-unit-tests + + prepare-agent + + + + + post-unit-test + test + + report + + + + pre-integration-tests + + prepare-agent-integration + + + + + post-integration-tests + post-integration-test + + report-integration + + + + + + org.liquibase + liquibase-maven-plugin + ${liquibase.version} + + config/liquibase/master.xml + ${project.basedir}/src/main/resources/config/liquibase/changelog/${maven.build.timestamp}_changelog.xml + com.mysql.cj.jdbc.Driver + ${liquibase-plugin.url} + + ${liquibase-plugin.username} + ${liquibase-plugin.password} + hibernate:spring:de.novatec.ittage.app.domain?dialect=org.hibernate.dialect.MySQL8Dialect&hibernate.physical_naming_strategy=org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy&hibernate.implicit_naming_strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + true + debug + !test + + + + org.liquibase + liquibase-core + ${liquibase.version} + + + org.liquibase.ext + liquibase-hibernate6 + ${liquibase.version} + + + org.springframework.boot + spring-boot-starter-data-jpa + ${spring-boot.version} + + + jakarta.validation + jakarta.validation-api + ${validation-api.version} + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + ${sonar-maven-plugin.version} + + + + + + + + api-docs + + ,api-docs + + + + dev + + true + + + + dev${profile.tls}${profile.no-liquibase} + testdev + jdbc:mysql://localhost:3306/app + root + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + e2e + + ,e2e + + + e2e + + + + org.springframework.boot + spring-boot-maven-plugin + + + repackage + + repackage + + + + + + + + + + + eclipse + + + m2e.version + + + + + + org.springframework.boot + spring-boot-starter-undertow + + + + + + + + org.eclipse.m2e + lifecycle-mapping + ${lifecycle-mapping.version} + + + + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + prepare-agent + + + + + + + + + com.github.eirslett + frontend-maven-plugin + ${frontend-maven-plugin.version} + + install-node-and-npm + npm + + + + + + + + + + + + + + + + + IDE + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + + + org.hibernate.orm + hibernate-jpamodelgen + + + + + no-liquibase + + ,no-liquibase + + + + prod + + + prod${profile.api-docs}${profile.tls}${profile.e2e}${profile.no-liquibase} + testprod + jdbc:mysql://localhost:3306/app + root + + + + + + org.apache.maven.plugins + maven-clean-plugin + + + + target/classes/static/ + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + build-info + + + + + + com.github.eirslett + frontend-maven-plugin + + + install-node-and-npm + + install-node-and-npm + + + + npm install + + npm + + + + webapp build test + + npm + + test + + run webapp:test + false + + + + webapp build prod + + npm + + generate-resources + + run webapp:prod + + ${project.version} + + false + + + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + + + + tls + + ,tls + + + + war + + + + org.apache.maven.plugins + maven-war-plugin + + + + + + webapp + + true + + + + + net.nicoulaj.maven.plugins + checksum-maven-plugin + ${checksum-maven-plugin.version} + + + create-pre-compiled-webapp-checksum + + files + + generate-resources + + + create-compiled-webapp-checksum + + files + + compile + + checksums.csv.old + + + + + + + ${project.basedir} + + src/main/webapp/**/*.* + target/classes/static/**/*.* + package-lock.json + package.json + tsconfig.json + tsconfig.app.json + webpack/*.* + + + **/app/**/service-worker.js + **/app/**/vendor.css + + + + false + false + false + + SHA-1 + + true + true + + + + org.apache.maven.plugins + maven-antrun-plugin + ${maven-antrun-plugin.version} + + + eval-frontend-checksum + generate-resources + + run + + + + + + + + + + + + true + + + + + + com.github.eirslett + frontend-maven-plugin + + + install-node-and-npm + + install-node-and-npm + + + + npm install + + npm + + + + webapp build dev + + npm + + generate-resources + + run webapp:build + + ${project.version} + + false + + + + + + + + + dev${profile.no-liquibase} + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/sonar-project.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/sonar-project.properties new file mode 100644 index 0000000..1e420f8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/sonar-project.properties @@ -0,0 +1,40 @@ +sonar.projectKey = app +sonar.projectName = app generated by jhipster + +# Typescript tests files must be inside sources and tests, othewise `INFO: Test execution data ignored for 80 unknown files, including:` is +# shown. +sonar.sources = src +sonar.tests = src +sonar.host.url = http://localhost:9001 + +sonar.test.inclusions = src/test/**/*.*, src/main/webapp/app/**/*.spec.ts +sonar.coverage.jacoco.xmlReportPaths = target/site/**/jacoco*.xml +sonar.java.codeCoveragePlugin = jacoco +sonar.junit.reportPaths = target/surefire-reports,target/failsafe-reports +sonar.testExecutionReportPaths = target/test-results/jest/TESTS-results-sonar.xml +sonar.javascript.lcov.reportPaths = target/test-results/lcov.info + +sonar.sourceEncoding = UTF-8 +sonar.exclusions = src/main/webapp/content/**/*.*, src/main/webapp/i18n/*.js, target/classes/static/**/*.* + +sonar.issue.ignore.multicriteria = S2187,S3437,S4502,S4684,S5145,UndocumentedApi + +# Rule https://rules.sonarsource.com/java/RSPEC-2187 is ignored, gatling tests are not supported by sonar +sonar.issue.ignore.multicriteria.S2187.resourceKey = src/test/java/gatling/**/* +sonar.issue.ignore.multicriteria.S2187.ruleKey = java:S2187 +# Rule https://rules.sonarsource.com/java/RSPEC-3437 is ignored, as a JPA-managed field cannot be transient +sonar.issue.ignore.multicriteria.S3437.resourceKey = src/main/java/**/* +sonar.issue.ignore.multicriteria.S3437.ruleKey = squid:S3437 +# Rule https://rules.sonarsource.com/java/RSPEC-4502 is ignored, as for JWT tokens we are not subject to CSRF attack +sonar.issue.ignore.multicriteria.S4502.resourceKey = src/main/java/**/* +sonar.issue.ignore.multicriteria.S4502.ruleKey = java:S4502 +# Rule https://rules.sonarsource.com/java/RSPEC-4684 +sonar.issue.ignore.multicriteria.S4684.resourceKey = src/main/java/**/* +sonar.issue.ignore.multicriteria.S4684.ruleKey = java:S4684 +# Rule https://rules.sonarsource.com/java/RSPEC-5145 log filter is applied +sonar.issue.ignore.multicriteria.S5145.resourceKey = src/main/java/**/* +sonar.issue.ignore.multicriteria.S5145.ruleKey = javasecurity:S5145 +# Rule https://rules.sonarsource.com/java/RSPEC-1176 is ignored, as we want to follow "clean code" guidelines and classes, methods and +# arguments names should be self-explanatory +sonar.issue.ignore.multicriteria.UndocumentedApi.resourceKey = src/main/java/**/* +sonar.issue.ignore.multicriteria.UndocumentedApi.ruleKey = squid:UndocumentedApi diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/app.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/app.yml new file mode 100644 index 0000000..599711d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/app.yml @@ -0,0 +1,29 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: app +services: + app: + image: app + environment: + - _JAVA_OPTIONS=-Xmx512m -Xms256m + - SPRING_PROFILES_ACTIVE=prod,api-docs + - MANAGEMENT_PROMETHEUS_METRICS_EXPORT_ENABLED=true + - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/app?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true + - SPRING_LIQUIBASE_URL=jdbc:mysql://mysql:3306/app?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true + ports: + - 127.0.0.1:8080:8080 + healthcheck: + test: + - CMD + - curl + - -f + - http://localhost:8080/management/health + interval: 5s + timeout: 5s + retries: 40 + depends_on: + mysql: + condition: service_healthy + mysql: + extends: + file: ./mysql.yml + service: mysql diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/config/mysql/my.cnf b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/config/mysql/my.cnf new file mode 100644 index 0000000..582bdd1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/config/mysql/my.cnf @@ -0,0 +1,82 @@ +# For advice on how to change settings please see +# http://dev.mysql.com/doc/refman/5.7/en/server-configuration-defaults.html +[mysqld] +user = mysql +datadir = /var/lib/mysql +port = 3306 +#socket = /tmp/mysql.sock +skip-external-locking +key_buffer_size = 16K +max_allowed_packet = 1M +table_open_cache = 4 +sort_buffer_size = 64K +read_buffer_size = 256K +read_rnd_buffer_size = 256K +net_buffer_length = 2K +skip-host-cache +skip-name-resolve + +# Don't listen on a TCP/IP port at all. This can be a security enhancement, +# if all processes that need to connect to mysqld run on the same host. +# All interaction with mysqld must be made via Unix sockets or named pipes. +# Note that using this option without enabling named pipes on Windows +# (using the "enable-named-pipe" option) will render mysqld useless! +# +#skip-networking +#server-id = 1 + +# Uncomment the following if you want to log updates +#log-bin=mysql-bin + +# binary logging format - mixed recommended +#binlog_format=mixed + +# Causes updates to non-transactional engines using statement format to be +# written directly to binary log. Before using this option make sure that +# there are no dependencies between transactional and non-transactional +# tables such as in the statement INSERT INTO t_myisam SELECT * FROM +# t_innodb; otherwise, slaves may diverge from the master. +#binlog_direct_non_transactional_updates=TRUE + +# Uncomment the following if you are using InnoDB tables +innodb_data_file_path = ibdata1:10M:autoextend +# You can set .._buffer_pool_size up to 50 - 80 % +# of RAM but beware of setting memory usage too high +innodb_buffer_pool_size = 16M +#innodb_additional_mem_pool_size = 2M +# Set .._log_file_size to 25 % of buffer pool size +innodb_log_file_size = 5M +innodb_log_buffer_size = 8M +innodb_flush_log_at_trx_commit = 1 +innodb_lock_wait_timeout = 50 + +symbolic-links=0 +innodb_buffer_pool_size=5M +innodb_log_buffer_size=256K +max_connections=20 +key_buffer_size=8 +thread_cache_size=0 +host_cache_size=0 +innodb_ft_cache_size=1600000 +innodb_ft_total_cache_size=32000000 +#### These optimize the memory use of MySQL +#### http://www.tocker.ca/2014/03/10/configuring-mysql-to-use-minimal-memory.html + +# per thread or per operation settings +thread_stack=131072 +sort_buffer_size=32K +read_buffer_size=8200 +read_rnd_buffer_size=8200 +max_heap_table_size=16K +tmp_table_size=1K +bulk_insert_buffer_size=0 +join_buffer_size=128 +net_buffer_length=1K +innodb_sort_buffer_size=64K + +#settings that relate to the binary log (if enabled) +binlog_cache_size=4K +binlog_stmt_cache_size=4K + +performance_schema = off +character-set-server = utf8mb4 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/docker-registry.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/docker-registry.yml new file mode 100644 index 0000000..24ad73b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/docker-registry.yml @@ -0,0 +1,39 @@ +# Documentation at https://docs.docker.com/registry/deploying +version: '2' +services: + registry: + image: registry:2 + ports: + - 5000:5000 + #environment: + # uncomment to enable basic auth + #- REGISTRY_AUTH=htpasswd + #- REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd + #- REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm + + # uncomment to enable tls with a certificate + #- REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt + #- REGISTRY_HTTP_TLS_KEY=/certs/domain.key + + # uncomment to enable tls with letsencrypt + #- REGISTRY_HTTP_TLS_LETSENCRYPT_CACHEFILE=/path/to/cache-file + #- REGISTRY_HTTP_TLS_LETSENCRYPT_EMAIL=mail@example.com + #volumes: + # uncomment to enable basic auth + # you will need to generate a `htpasswd` file using : + # docker run --entrypoint htpasswd registry:2 -Bbn admin admin > ~/volumes/registry/htpasswd + #- ~/volumes/registry/htpasswd:/auth/htpasswd:ro + + # uncomment to enable tls + #- ~/volumes/registry/certs:/certs + + # uncomment to persist data to a volume + #- ~/volumes/registry/data:/var/lib/registry + registry-ui: + image: konradkleine/docker-registry-frontend:v2 + environment: + - ENV_DOCKER_REGISTRY_HOST=registry + - ENV_DOCKER_REGISTRY_PORT=5000 + #- ENV_DOCKER_REGISTRY_USE_SSL=1 + ports: + - 5080:80 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/JVM.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/JVM.json new file mode 100644 index 0000000..5104abc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/JVM.json @@ -0,0 +1,3778 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "limit": 100, + "name": "Annotations & Alerts", + "showIn": 0, + "type": "dashboard" + }, + { + "datasource": "Prometheus", + "enable": true, + "expr": "resets(process_uptime_seconds{application=\"$application\", instance=\"$instance\"}[1m]) > 0", + "iconColor": "rgba(255, 96, 96, 1)", + "name": "Restart Detection", + "showIn": 0, + "step": "1m", + "tagKeys": "restart-tag", + "textFormat": "uptime reset", + "titleFormat": "Restart" + } + ] + }, + "description": "Dashboard for Micrometer instrumented applications (Java, Spring Boot, Micronaut)", + "editable": true, + "gnetId": 4701, + "graphTooltip": 1, + "iteration": 1553765841423, + "links": [], + "panels": [ + { + "content": "\n# Acknowledgments\n\nThank you to [Michael Weirauch](https://twitter.com/emwexx) for creating this dashboard: see original JVM (Micrometer) dashboard at [https://grafana.com/dashboards/4701](https://grafana.com/dashboards/4701)\n\n\n\n", + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 141, + "links": [], + "mode": "markdown", + "timeFrom": null, + "timeShift": null, + "title": "Acknowledgments", + "type": "text" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 3 + }, + "id": 125, + "panels": [], + "repeat": null, + "title": "Quick Facts", + "type": "row" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + "datasource": "Prometheus", + "decimals": 1, + "editable": true, + "error": false, + "format": "s", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 0, + "y": 4 + }, + "height": "", + "id": 63, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "process_uptime_seconds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "", + "title": "Uptime", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"], + "datasource": "Prometheus", + "decimals": null, + "editable": true, + "error": false, + "format": "dateTimeAsIso", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 6, + "y": 4 + }, + "height": "", + "id": 92, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "process_start_time_seconds{application=\"$application\", instance=\"$instance\"}*1000", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "metric": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "", + "title": "Start time", + "type": "singlestat", + "valueFontSize": "70%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 12, + "y": 4 + }, + "id": 65, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "70,90", + "title": "Heap used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": false, + "colorValue": true, + "colors": ["rgba(50, 172, 45, 0.97)", "rgba(237, 129, 40, 0.89)", "rgba(245, 54, 54, 0.9)"], + "datasource": "Prometheus", + "decimals": 2, + "editable": true, + "error": false, + "format": "percent", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 6, + "x": 18, + "y": 4 + }, + "id": 75, + "interval": null, + "links": [], + "mappingType": 2, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "postfix": "", + "postfixFontSize": "50%", + "prefix": "", + "prefixFontSize": "70%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + }, + { + "from": "-99999999999999999999999999999999", + "text": "N/A", + "to": "0" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false + }, + "tableColumn": "", + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})*100/sum(jvm_memory_max_bytes{application=\"$application\",instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "", + "refId": "A", + "step": 14400 + } + ], + "thresholds": "70,90", + "title": "Non-Heap used", + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + }, + { + "op": "=", + "text": "x", + "value": "" + } + ], + "valueName": "current" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 7 + }, + "id": 126, + "panels": [], + "repeat": null, + "title": "I/O Overview", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 8 + }, + "id": 111, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rate", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "HTTP": "#890f02", + "HTTP - 5xx": "#bf1b00" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 8 + }, + "id": 112, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status=~\"5..\"}[1m]))", + "format": "time_series", + "intervalFactor": 1, + "legendFormat": "HTTP - 5xx", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Errors", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 8 + }, + "id": 113, + "legend": { + "avg": false, + "current": true, + "max": false, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(rate(http_server_requests_seconds_sum{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))/sum(rate(http_server_requests_seconds_count{application=\"$application\", instance=\"$instance\", status!~\"5..\"}[1m]))", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - AVG", + "refId": "A" + }, + { + "expr": "max(http_server_requests_seconds_max{application=\"$application\", instance=\"$instance\", status!~\"5..\"})", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "HTTP - MAX", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Duration", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 127, + "panels": [], + "repeat": null, + "title": "JVM Memory", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 16 + }, + "id": 24, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 16 + }, + "id": 25, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Non-Heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 16 + }, + "id": 26, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "sum(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "committed", + "refId": "B", + "step": 2400 + }, + { + "expr": "sum(jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\"})", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "C", + "step": 2400 + }, + { + "expr": "process_memory_vss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": true, + "intervalFactor": 2, + "legendFormat": "vss", + "metric": "", + "refId": "D", + "step": 2400 + }, + { + "expr": "process_memory_rss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "rss", + "refId": "E", + "step": 2400 + }, + { + "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "pss", + "refId": "F", + "step": 2400 + }, + { + "expr": "process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "swap", + "refId": "G", + "step": 2400 + }, + { + "expr": "process_memory_swappss_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "swappss", + "refId": "H", + "step": 2400 + }, + { + "expr": "process_memory_pss_bytes{application=\"$application\", instance=\"$instance\"} + process_memory_swap_bytes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "phys (pss+swap)", + "refId": "I", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JVM Total", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": "", + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 23 + }, + "id": 128, + "panels": [], + "repeat": null, + "title": "JVM Misc", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 24 + }, + "id": 106, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "system", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "process_cpu_usage{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process", + "refId": "B" + }, + { + "expr": "avg_over_time(process_cpu_usage{application=\"$application\", instance=\"$instance\"}[1h])", + "format": "time_series", + "hide": false, + "intervalFactor": 1, + "legendFormat": "process-1h", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 1, + "format": "percentunit", + "label": "", + "logBase": 1, + "max": "1", + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 24 + }, + "id": 93, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "system_load_average_1m{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "system-1m", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "", + "format": "time_series", + "intervalFactor": 2, + "refId": "B" + }, + { + "expr": "system_cpu_count{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "cpu", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Load", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 1, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 24 + }, + "id": 32, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_threads_live{application=\"$application\", instance=\"$instance\"} or jvm_threads_live_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "live", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_threads_daemon{application=\"$application\", instance=\"$instance\"} or jvm_threads_daemon_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "daemon", + "metric": "", + "refId": "B", + "step": 2400 + }, + { + "expr": "jvm_threads_peak{application=\"$application\", instance=\"$instance\"} or jvm_threads_peak_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "peak", + "refId": "C", + "step": 2400 + }, + { + "expr": "process_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "process", + "refId": "D", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Threads", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "blocked": "#bf1b00", + "new": "#fce2de", + "runnable": "#7eb26d", + "terminated": "#511749", + "timed-waiting": "#c15c17", + "waiting": "#eab839" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 24 + }, + "id": 124, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_threads_states_threads{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "{{state}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Thread States", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "debug": "#1F78C1", + "error": "#BF1B00", + "info": "#508642", + "trace": "#6ED0E0", + "warn": "#EAB839" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 18, + "x": 0, + "y": 31 + }, + "height": "", + "id": 91, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "hideEmpty": false, + "hideZero": false, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": true, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "error", + "yaxis": 1 + }, + { + "alias": "warn", + "yaxis": 1 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "increase(logback_events_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 2, + "legendFormat": "{{level}}", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Log Events (1m)", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 31 + }, + "id": 61, + "legend": { + "avg": false, + "current": true, + "max": true, + "min": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "process_open_fds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "open", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "process_max_fds{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "B", + "step": 2400 + }, + { + "expr": "process_files_open{application=\"$application\", instance=\"$instance\"} or process_files_open_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "open", + "refId": "C" + }, + { + "expr": "process_files_max{application=\"$application\", instance=\"$instance\"} or process_files_max_files{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "max", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "File Descriptors", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 10, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 38 + }, + "id": 129, + "panels": [], + "repeat": "persistence_counts", + "title": "JVM Memory Pools (Heap)", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 39 + }, + "id": 3, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_memory_pool_heap", + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Eden Space", + "value": "PS Eden Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 39 + }, + "id": 134, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 3, + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Old Gen", + "value": "PS Old Gen" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 39 + }, + "id": 135, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 3, + "scopedVars": { + "jvm_memory_pool_heap": { + "selected": false, + "text": "PS Survivor Space", + "value": "PS Survivor Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_heap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_heap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 46 + }, + "id": 130, + "panels": [], + "repeat": null, + "title": "JVM Memory Pools (Non-Heap)", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 47 + }, + "id": 78, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": "jvm_memory_pool_nonheap", + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Metaspace", + "value": "Metaspace" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 47 + }, + "id": 136, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 78, + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Compressed Class Space", + "value": "Compressed Class Space" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 47 + }, + "id": 137, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": true, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxPerRow": 3, + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "repeat": null, + "repeatIteration": 1553765841423, + "repeatPanelId": 78, + "scopedVars": { + "jvm_memory_pool_nonheap": { + "selected": false, + "text": "Code Cache", + "value": "Code Cache" + } + }, + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 1800 + }, + { + "expr": "jvm_memory_committed_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "commited", + "metric": "", + "refId": "B", + "step": 1800 + }, + { + "expr": "jvm_memory_max_bytes{application=\"$application\", instance=\"$instance\", id=\"$jvm_memory_pool_nonheap\"}", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "max", + "metric": "", + "refId": "C", + "step": 1800 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "$jvm_memory_pool_nonheap", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["mbytes", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 54 + }, + "id": 131, + "panels": [], + "repeat": null, + "title": "Garbage Collection", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 55 + }, + "id": 98, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "intervalFactor": 2, + "legendFormat": "{{action}} ({{cause}})", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Collections", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "ops", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 8, + "y": 55 + }, + "id": 101, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_pause_seconds_sum{application=\"$application\", instance=\"$instance\"}[1m])/rate(jvm_gc_pause_seconds_count{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "avg {{action}} ({{cause}})", + "refId": "A" + }, + { + "expr": "jvm_gc_pause_seconds_max{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "hide": false, + "instant": false, + "intervalFactor": 1, + "legendFormat": "max {{action}} ({{cause}})", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Pause Durations", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "s", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "fill": 1, + "gridPos": { + "h": 7, + "w": 8, + "x": 16, + "y": 55 + }, + "id": 99, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "rate(jvm_gc_memory_allocated_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "allocated", + "refId": "A" + }, + { + "expr": "rate(jvm_gc_memory_promoted_bytes_total{application=\"$application\", instance=\"$instance\"}[1m])", + "format": "time_series", + "interval": "", + "intervalFactor": 1, + "legendFormat": "promoted", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Allocated/Promoted", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 62 + }, + "id": 132, + "panels": [], + "repeat": null, + "title": "Classloading", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 63 + }, + "id": 37, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_classes_loaded{application=\"$application\", instance=\"$instance\"} or jvm_classes_loaded_classes{application=\"$application\", instance=\"$instance\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "loaded", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Classes loaded", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 63 + }, + "id": 38, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "delta(jvm_classes_loaded{application=\"$application\",instance=\"$instance\"}[5m]) or delta(jvm_classes_loaded_classes{application=\"$application\",instance=\"$instance\"}[5m])", + "format": "time_series", + "hide": false, + "interval": "", + "intervalFactor": 2, + "legendFormat": "delta", + "metric": "", + "refId": "A", + "step": 1200 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Class delta (5m)", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["ops", "short"], + "yaxes": [ + { + "decimals": null, + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 70 + }, + "id": 133, + "panels": [], + "repeat": null, + "title": "Buffer Pools", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 0, + "y": 71 + }, + "id": 33, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "capacity", + "metric": "", + "refId": "B", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Direct Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 6, + "y": 71 + }, + "id": 83, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"direct\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"direct\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "count", + "metric": "", + "refId": "A", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Direct Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 12, + "y": 71 + }, + "id": 85, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_memory_used_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "used", + "metric": "", + "refId": "A", + "step": 2400 + }, + { + "expr": "jvm_buffer_total_capacity_bytes{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "capacity", + "metric": "", + "refId": "B", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mapped Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "format": "bytes", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "Prometheus", + "editable": true, + "error": false, + "fill": 1, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null + }, + "gridPos": { + "h": 7, + "w": 6, + "x": 18, + "y": 71 + }, + "id": 84, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "paceLength": 10, + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "expr": "jvm_buffer_count{application=\"$application\", instance=\"$instance\", id=\"mapped\"} or jvm_buffer_count_buffers{application=\"$application\", instance=\"$instance\", id=\"mapped\"}", + "format": "time_series", + "intervalFactor": 2, + "legendFormat": "count", + "metric": "", + "refId": "A", + "step": 2400 + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Mapped Buffers", + "tooltip": { + "msResolution": false, + "shared": true, + "sort": 0, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "y-axis": true, + "y_formats": ["short", "short"], + "yaxes": [ + { + "decimals": 0, + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": 0, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 18, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "text": "test", + "value": "test" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Application", + "multi": false, + "name": "application", + "options": [], + "query": "label_values(application)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "localhost:8080", + "value": "localhost:8080" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": false, + "label": "Instance", + "multi": false, + "multiFormat": "glob", + "name": "instance", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\"}, instance)", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": true, + "label": "JVM Memory Pools Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_heap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"heap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 1, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "allFormat": "glob", + "allValue": null, + "current": { + "text": "All", + "value": "$__all" + }, + "datasource": "Prometheus", + "definition": "", + "hide": 0, + "includeAll": true, + "label": "JVM Memory Pools Non-Heap", + "multi": false, + "multiFormat": "glob", + "name": "jvm_memory_pool_nonheap", + "options": [], + "query": "label_values(jvm_memory_used_bytes{application=\"$application\", instance=\"$instance\", area=\"nonheap\"},id)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 2, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "now": true, + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"], + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"] + }, + "timezone": "browser", + "title": "JVM (Micrometer)", + "uid": "Ud1CFe3iz", + "version": 1 +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/dashboard.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000..4817a83 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,11 @@ +apiVersion: 1 + +providers: + - name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/datasources/datasource.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000..57b2bb3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,50 @@ +apiVersion: 1 + +# list of datasources that should be deleted from the database +deleteDatasources: + - name: Prometheus + orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required + - name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + # On MacOS, replace localhost by host.docker.internal + url: http://localhost:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username + basicAuthUser: admin + # basic auth password + basicAuthPassword: admin + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: '1.1' + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: '...' + tlsClientCert: '...' + tlsClientKey: '...' + version: 1 + # allow users to edit datasources from the UI. + editable: true diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jhipster-control-center.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jhipster-control-center.yml new file mode 100644 index 0000000..83647ca --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jhipster-control-center.yml @@ -0,0 +1,51 @@ +## How to use JHCC docker compose +# To allow JHCC to reach JHipster application from a docker container note that we set the host as host.docker.internal +# To reach the application from a browser, you need to add '127.0.0.1 host.docker.internal' to your hosts file. +### Discovery mode +# JHCC support 3 kinds of discovery mode: Consul, Eureka and static +# In order to use one, please set SPRING_PROFILES_ACTIVE to one (and only one) of this values: consul,eureka,static +### Discovery properties +# According to the discovery mode choose as Spring profile, you have to set the right properties +# please note that current properties are set to run JHCC with default values, personalize them if needed +# and remove those from other modes. You can only have one mode active. +#### Eureka +# - EUREKA_CLIENT_SERVICE_URL_DEFAULTZONE=http://admin:admin@host.docker.internal:8761/eureka/ +#### Consul +# - SPRING_CLOUD_CONSUL_HOST=host.docker.internal +# - SPRING_CLOUD_CONSUL_PORT=8500 +#### Static +# Add instances to "MyApp" +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_0_URI=http://host.docker.internal:8081 +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYAPP_1_URI=http://host.docker.internal:8082 +# Or add a new application named MyNewApp +# - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_MYNEWAPP_0_URI=http://host.docker.internal:8080 +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production + +#### IMPORTANT +# If you choose Consul or Eureka mode: +# Do not forget to remove the prefix "127.0.0.1" in front of their port in order to expose them. +# This is required because JHCC need to communicate with Consul or Eureka. +# - In Consul mode, the ports are in the consul.yml file. +# - In Eureka mode, the ports are in the jhipster-registry.yml file. + +name: app +services: + jhipster-control-center: + image: 'jhipster/jhipster-control-center:v0.5.0' + command: + - /bin/sh + - -c + # Patch /etc/hosts to support resolving host.docker.internal to the internal IP address used by the host in all OSes + - echo "`ip route | grep default | cut -d ' ' -f3` host.docker.internal" | tee -a /etc/hosts > /dev/null && java -jar /jhipster-control-center.jar + environment: + - _JAVA_OPTIONS=-Xmx512m -Xms256m + - SPRING_PROFILES_ACTIVE=prod,api-docs,static + - SPRING_SECURITY_USER_PASSWORD=admin + # The token should have the same value than the one declared in you Spring configuration under the jhipster.security.authentication.jwt.base64-secret configuration's entry + - JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET=MmQ3MzkwYzgzMzMxOTYwN2IxMGViYTM4MjlmNThkN2JhMzNkMDQ5ZjIyNzJkNDhlNWNkZGNhZDdjYzRiNzIyMjVlYTAwMmI5NTA3ZmIxZTQzODNhZWY5OWFjZTBiNTVmODlhMzY4NDhjMmQ1Y2UzNGMwMTEzNWFiNWU5YWEwMTY= + - SPRING_CLOUD_DISCOVERY_CLIENT_SIMPLE_INSTANCES_APP_0_URI=http://host.docker.internal:8080 + - LOGGING_FILE_NAME=/tmp/jhipster-control-center.log + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:7419:7419 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jib/entrypoint.sh b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jib/entrypoint.sh new file mode 100644 index 0000000..e85d74f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/jib/entrypoint.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +echo "The application will start in ${JHIPSTER_SLEEP}s..." && sleep ${JHIPSTER_SLEEP} + +# usage: file_env VAR [DEFAULT] +# ie: file_env 'XYZ_DB_PASSWORD' 'example' +# (will allow for "$XYZ_DB_PASSWORD_FILE" to fill in the value of +# "$XYZ_DB_PASSWORD" from a file, especially for Docker's secrets feature) +file_env() { + local var="$1" + local fileVar="${var}_FILE" + local def="${2:-}" + if [[ ${!var:-} && ${!fileVar:-} ]]; then + echo >&2 "error: both $var and $fileVar are set (but are exclusive)" + exit 1 + fi + local val="$def" + if [[ ${!var:-} ]]; then + val="${!var}" + elif [[ ${!fileVar:-} ]]; then + val="$(< "${!fileVar}")" + fi + + if [[ -n $val ]]; then + export "$var"="$val" + fi + + unset "$fileVar" +} + +file_env 'SPRING_DATASOURCE_URL' +file_env 'SPRING_DATASOURCE_USERNAME' +file_env 'SPRING_DATASOURCE_PASSWORD' +file_env 'SPRING_LIQUIBASE_URL' +file_env 'SPRING_LIQUIBASE_USER' +file_env 'SPRING_LIQUIBASE_PASSWORD' +file_env 'JHIPSTER_REGISTRY_PASSWORD' + +exec java ${JAVA_OPTS} -noverify -XX:+AlwaysPreTouch -Djava.security.egd=file:/dev/./urandom -cp /app/resources/:/app/classes/:/app/libs/* "de.novatec.ittage.app.AppApp" "$@" diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/monitoring.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/monitoring.yml new file mode 100644 index 0000000..39efce0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/monitoring.yml @@ -0,0 +1,31 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: app +services: + prometheus: + image: prom/prometheus:v2.47.2 + volumes: + - ./prometheus/:/etc/prometheus/ + command: + - '--config.file=/etc/prometheus/prometheus.yml' + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:9090:9090 + # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and + # grafana/provisioning/datasources/datasource.yml + network_mode: 'host' # to test locally running service + grafana: + image: grafana/grafana:10.2.0 + volumes: + - ./grafana/provisioning/:/etc/grafana/provisioning/ + environment: + - GF_SECURITY_ADMIN_PASSWORD=admin + - GF_USERS_ALLOW_SIGN_UP=false + - GF_INSTALL_PLUGINS=grafana-piechart-panel + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:3000:3000 + # On MacOS, remove next line and replace localhost by host.docker.internal in prometheus/prometheus.yml and + # grafana/provisioning/datasources/datasource.yml + network_mode: 'host' # to test locally running service diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/mysql.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/mysql.yml new file mode 100644 index 0000000..33aad5c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/mysql.yml @@ -0,0 +1,21 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: app +services: + mysql: + image: mysql:8.2.0 + volumes: + - ./config/mysql:/etc/mysql/conf.d + # - ~/volumes/jhipster/app/mysql/:/var/lib/mysql/ + environment: + - MYSQL_ALLOW_EMPTY_PASSWORD=yes + - MYSQL_DATABASE=app + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:3306:3306 + command: mysqld --lower_case_table_names=1 --skip-ssl --character_set_server=utf8mb4 --explicit_defaults_for_timestamp + healthcheck: + test: ['CMD', 'mysql', '-e', 'SHOW DATABASES;'] + interval: 5s + timeout: 5s + retries: 10 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/prometheus/prometheus.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/prometheus/prometheus.yml new file mode 100644 index 0000000..b370a2f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/prometheus/prometheus.yml @@ -0,0 +1,31 @@ +# Sample global config for monitoring JHipster applications +global: + scrape_interval: 15s # By default, scrape targets every 15 seconds. + evaluation_interval: 15s # By default, scrape targets every 15 seconds. + # scrape_timeout is set to the global default (10s). + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'jhipster' + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + - job_name: 'prometheus' + + # Override the global default and scrape targets from this job every 5 seconds. + scrape_interval: 5s + + # scheme defaults to 'http' enable https in case your application is server via https + #scheme: https + # basic auth is not needed by default. See https://www.jhipster.tech/monitoring/#configuring-metrics-forwarding for details + #basic_auth: + # username: admin + # password: admin + metrics_path: /management/prometheus + static_configs: + - targets: + # On MacOS, replace localhost by host.docker.internal + - localhost:8080 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/services.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/services.yml new file mode 100644 index 0000000..323b481 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/services.yml @@ -0,0 +1,7 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: app +services: + mysql: + extends: + file: ./mysql.yml + service: mysql diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/sonar.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/sonar.yml new file mode 100644 index 0000000..2372216 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/docker/sonar.yml @@ -0,0 +1,15 @@ +# This configuration is intended for development purpose, it's **your** responsibility to harden it for production +name: app +services: + sonar: + container_name: sonarqube + image: sonarqube:10.2.1-community + # Forced authentication redirect for UI is turned off for out of the box experience while trying out SonarQube + # For real use cases delete SONAR_FORCEAUTHENTICATION variable or set SONAR_FORCEAUTHENTICATION=true + environment: + - SONAR_FORCEAUTHENTICATION=false + # If you want to expose these ports outside your dev PC, + # remove the "127.0.0.1:" prefix + ports: + - 127.0.0.1:9001:9000 + - 127.0.0.1:9000:9000 diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/AppApp.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/AppApp.java new file mode 100644 index 0000000..cabe4b0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/AppApp.java @@ -0,0 +1,109 @@ +package de.novatec.ittage.app; + +import de.novatec.ittage.app.config.ApplicationProperties; +import de.novatec.ittage.app.config.CRLFLogConverter; +import jakarta.annotation.PostConstruct; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.core.env.Environment; +import tech.jhipster.config.DefaultProfileUtil; +import tech.jhipster.config.JHipsterConstants; + +@SpringBootApplication +@EnableConfigurationProperties({ LiquibaseProperties.class, ApplicationProperties.class }) +public class AppApp { + + private static final Logger log = LoggerFactory.getLogger(AppApp.class); + + private final Environment env; + + public AppApp(Environment env) { + this.env = env; + } + + /** + * Initializes app. + *

+ * Spring profiles can be configured with a program argument --spring.profiles.active=your-active-profile + *

+ * You can find more information on how profiles work with JHipster on https://www.jhipster.tech/profiles/. + */ + @PostConstruct + public void initApplication() { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + if ( + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION) + ) { + log.error( + "You have misconfigured your application! It should not run " + "with both the 'dev' and 'prod' profiles at the same time." + ); + } + if ( + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) && + activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_CLOUD) + ) { + log.error( + "You have misconfigured your application! It should not " + "run with both the 'dev' and 'cloud' profiles at the same time." + ); + } + } + + /** + * Main method, used to run the application. + * + * @param args the command line arguments. + */ + public static void main(String[] args) { + SpringApplication app = new SpringApplication(AppApp.class); + DefaultProfileUtil.addDefaultProfile(app); + Environment env = app.run(args).getEnvironment(); + logApplicationStartup(env); + } + + private static void logApplicationStartup(Environment env) { + String protocol = Optional.ofNullable(env.getProperty("server.ssl.key-store")).map(key -> "https").orElse("http"); + String applicationName = env.getProperty("spring.application.name"); + String serverPort = env.getProperty("server.port"); + String contextPath = Optional + .ofNullable(env.getProperty("server.servlet.context-path")) + .filter(StringUtils::isNotBlank) + .orElse("/"); + String hostAddress = "localhost"; + try { + hostAddress = InetAddress.getLocalHost().getHostAddress(); + } catch (UnknownHostException e) { + log.warn("The host name could not be determined, using `localhost` as fallback"); + } + log.info( + CRLFLogConverter.CRLF_SAFE_MARKER, + """ + + ---------------------------------------------------------- + \tApplication '{}' is running! Access URLs: + \tLocal: \t\t{}://localhost:{}{} + \tExternal: \t{}://{}:{}{} + \tProfile(s): \t{} + ----------------------------------------------------------""", + applicationName, + protocol, + serverPort, + contextPath, + protocol, + hostAddress, + serverPort, + contextPath, + env.getActiveProfiles().length == 0 ? env.getDefaultProfiles() : env.getActiveProfiles() + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/ApplicationWebXml.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/ApplicationWebXml.java new file mode 100644 index 0000000..4ee0b08 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/ApplicationWebXml.java @@ -0,0 +1,19 @@ +package de.novatec.ittage.app; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; +import tech.jhipster.config.DefaultProfileUtil; + +/** + * This is a helper Java class that provides an alternative to creating a {@code web.xml}. + * This will be invoked only when the application is deployed to a Servlet container like Tomcat, JBoss etc. + */ +public class ApplicationWebXml extends SpringBootServletInitializer { + + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { + // set a default to use when no profile is configured. + DefaultProfileUtil.addDefaultProfile(application.application()); + return application.sources(AppApp.class); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/GeneratedByJHipster.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/GeneratedByJHipster.java new file mode 100644 index 0000000..ced8857 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/GeneratedByJHipster.java @@ -0,0 +1,13 @@ +package de.novatec.ittage.app; + +import jakarta.annotation.Generated; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Generated(value = "JHipster", comments = "Generated by JHipster 8.0.0") +@Retention(RetentionPolicy.SOURCE) +@Target({ ElementType.TYPE }) +public @interface GeneratedByJHipster { +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/LoggingAspect.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/LoggingAspect.java new file mode 100644 index 0000000..f2bb42d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/LoggingAspect.java @@ -0,0 +1,115 @@ +package de.novatec.ittage.app.aop.logging; + +import java.util.Arrays; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import tech.jhipster.config.JHipsterConstants; + +/** + * Aspect for logging execution of service and repository Spring components. + * + * By default, it only runs with the "dev" profile. + */ +@Aspect +public class LoggingAspect { + + private final Environment env; + + public LoggingAspect(Environment env) { + this.env = env; + } + + /** + * Pointcut that matches all repositories, services and Web REST endpoints. + */ + @Pointcut( + "within(@org.springframework.stereotype.Repository *)" + + " || within(@org.springframework.stereotype.Service *)" + + " || within(@org.springframework.web.bind.annotation.RestController *)" + ) + public void springBeanPointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Pointcut that matches all Spring beans in the application's main packages. + */ + @Pointcut( + "within(de.novatec.ittage.app.repository..*)" + + " || within(de.novatec.ittage.app.service..*)" + + " || within(de.novatec.ittage.app.web.rest..*)" + ) + public void applicationPackagePointcut() { + // Method is empty as this is just a Pointcut, the implementations are in the advices. + } + + /** + * Retrieves the {@link Logger} associated to the given {@link JoinPoint}. + * + * @param joinPoint join point we want the logger for. + * @return {@link Logger} associated to the given {@link JoinPoint}. + */ + private Logger logger(JoinPoint joinPoint) { + return LoggerFactory.getLogger(joinPoint.getSignature().getDeclaringTypeName()); + } + + /** + * Advice that logs methods throwing exceptions. + * + * @param joinPoint join point for advice. + * @param e exception. + */ + @AfterThrowing(pointcut = "applicationPackagePointcut() && springBeanPointcut()", throwing = "e") + public void logAfterThrowing(JoinPoint joinPoint, Throwable e) { + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT))) { + logger(joinPoint) + .error( + "Exception in {}() with cause = '{}' and exception = '{}'", + joinPoint.getSignature().getName(), + e.getCause() != null ? e.getCause() : "NULL", + e.getMessage(), + e + ); + } else { + logger(joinPoint) + .error( + "Exception in {}() with cause = {}", + joinPoint.getSignature().getName(), + e.getCause() != null ? String.valueOf(e.getCause()) : "NULL" + ); + } + } + + /** + * Advice that logs when a method is entered and exited. + * + * @param joinPoint join point for advice. + * @return result. + * @throws Throwable throws {@link IllegalArgumentException}. + */ + @Around("applicationPackagePointcut() && springBeanPointcut()") + public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable { + Logger log = logger(joinPoint); + if (log.isDebugEnabled()) { + log.debug("Enter: {}() with argument[s] = {}", joinPoint.getSignature().getName(), Arrays.toString(joinPoint.getArgs())); + } + try { + Object result = joinPoint.proceed(); + if (log.isDebugEnabled()) { + log.debug("Exit: {}() with result = {}", joinPoint.getSignature().getName(), result); + } + return result; + } catch (IllegalArgumentException e) { + log.error("Illegal argument: {} in {}()", Arrays.toString(joinPoint.getArgs()), joinPoint.getSignature().getName()); + throw e; + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/package-info.java new file mode 100644 index 0000000..cb26ab3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/aop/logging/package-info.java @@ -0,0 +1,4 @@ +/** + * Logging aspect. + */ +package de.novatec.ittage.app.aop.logging; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/ApplicationProperties.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/ApplicationProperties.java new file mode 100644 index 0000000..2fde462 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/ApplicationProperties.java @@ -0,0 +1,16 @@ +package de.novatec.ittage.app.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * Properties specific to App. + *

+ * Properties are configured in the {@code application.yml} file. + * See {@link tech.jhipster.config.JHipsterProperties} for a good example. + */ +@ConfigurationProperties(prefix = "application", ignoreUnknownFields = false) +public class ApplicationProperties { + // jhipster-needle-application-properties-property + // jhipster-needle-application-properties-property-getter + // jhipster-needle-application-properties-property-class +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/AsyncConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/AsyncConfiguration.java new file mode 100644 index 0000000..c18b3f3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/AsyncConfiguration.java @@ -0,0 +1,48 @@ +package de.novatec.ittage.app.config; + +import java.util.concurrent.Executor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; +import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import tech.jhipster.async.ExceptionHandlingAsyncTaskExecutor; + +@Configuration +@EnableAsync +@EnableScheduling +@Profile("!testdev & !testprod") +public class AsyncConfiguration implements AsyncConfigurer { + + private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class); + + private final TaskExecutionProperties taskExecutionProperties; + + public AsyncConfiguration(TaskExecutionProperties taskExecutionProperties) { + this.taskExecutionProperties = taskExecutionProperties; + } + + @Override + @Bean(name = "taskExecutor") + public Executor getAsyncExecutor() { + log.debug("Creating Async Task Executor"); + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); + executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); + executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); + executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); + return new ExceptionHandlingAsyncTaskExecutor(executor); + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { + return new SimpleAsyncUncaughtExceptionHandler(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CRLFLogConverter.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CRLFLogConverter.java new file mode 100644 index 0000000..43a4f09 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CRLFLogConverter.java @@ -0,0 +1,69 @@ +package de.novatec.ittage.app.config; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.pattern.CompositeConverter; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; +import org.springframework.boot.ansi.AnsiColor; +import org.springframework.boot.ansi.AnsiElement; +import org.springframework.boot.ansi.AnsiOutput; +import org.springframework.boot.ansi.AnsiStyle; + +/** + * Log filter to prevent attackers from forging log entries by submitting input containing CRLF characters. + * CRLF characters are replaced with a red colored _ character. + * + * @see Log Forging Description + * @see JHipster issue + */ +public class CRLFLogConverter extends CompositeConverter { + + public static final Marker CRLF_SAFE_MARKER = MarkerFactory.getMarker("CRLF_SAFE"); + + private static final String[] SAFE_LOGGERS = { + "org.hibernate", + "org.springframework.boot.autoconfigure", + "org.springframework.boot.diagnostics", + }; + private static final Map ELEMENTS; + + static { + Map ansiElements = new HashMap<>(); + ansiElements.put("faint", AnsiStyle.FAINT); + ansiElements.put("red", AnsiColor.RED); + ansiElements.put("green", AnsiColor.GREEN); + ansiElements.put("yellow", AnsiColor.YELLOW); + ansiElements.put("blue", AnsiColor.BLUE); + ansiElements.put("magenta", AnsiColor.MAGENTA); + ansiElements.put("cyan", AnsiColor.CYAN); + ELEMENTS = Collections.unmodifiableMap(ansiElements); + } + + @Override + protected String transform(ILoggingEvent event, String in) { + AnsiElement element = ELEMENTS.get(getFirstOption()); + List markers = event.getMarkerList(); + if ((markers != null && !markers.isEmpty() && markers.get(0).contains(CRLF_SAFE_MARKER)) || isLoggerSafe(event)) { + return in; + } + String replacement = element == null ? "_" : toAnsiString("_", element); + return in.replaceAll("[\n\r\t]", replacement); + } + + protected boolean isLoggerSafe(ILoggingEvent event) { + for (String safeLogger : SAFE_LOGGERS) { + if (event.getLoggerName().startsWith(safeLogger)) { + return true; + } + } + return false; + } + + protected String toAnsiString(String in, AnsiElement element) { + return AnsiOutput.toString(element, in); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CacheConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CacheConfiguration.java new file mode 100644 index 0000000..452ca24 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/CacheConfiguration.java @@ -0,0 +1,78 @@ +package de.novatec.ittage.app.config; + +import java.time.Duration; +import org.ehcache.config.builders.*; +import org.ehcache.jsr107.Eh107Configuration; +import org.hibernate.cache.jcache.ConfigSettings; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer; +import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; +import org.springframework.boot.info.BuildProperties; +import org.springframework.boot.info.GitProperties; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.interceptor.KeyGenerator; +import org.springframework.context.annotation.*; +import tech.jhipster.config.JHipsterProperties; +import tech.jhipster.config.cache.PrefixedKeyGenerator; + +@Configuration +@EnableCaching +public class CacheConfiguration { + + private GitProperties gitProperties; + private BuildProperties buildProperties; + private final javax.cache.configuration.Configuration jcacheConfiguration; + + public CacheConfiguration(JHipsterProperties jHipsterProperties) { + JHipsterProperties.Cache.Ehcache ehcache = jHipsterProperties.getCache().getEhcache(); + + jcacheConfiguration = + Eh107Configuration.fromEhcacheCacheConfiguration( + CacheConfigurationBuilder + .newCacheConfigurationBuilder(Object.class, Object.class, ResourcePoolsBuilder.heap(ehcache.getMaxEntries())) + .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofSeconds(ehcache.getTimeToLiveSeconds()))) + .build() + ); + } + + @Bean + public HibernatePropertiesCustomizer hibernatePropertiesCustomizer(javax.cache.CacheManager cacheManager) { + return hibernateProperties -> hibernateProperties.put(ConfigSettings.CACHE_MANAGER, cacheManager); + } + + @Bean + public JCacheManagerCustomizer cacheManagerCustomizer() { + return cm -> { + createCache(cm, de.novatec.ittage.app.repository.UserRepository.USERS_BY_LOGIN_CACHE); + createCache(cm, de.novatec.ittage.app.repository.UserRepository.USERS_BY_EMAIL_CACHE); + createCache(cm, de.novatec.ittage.app.domain.User.class.getName()); + createCache(cm, de.novatec.ittage.app.domain.Authority.class.getName()); + createCache(cm, de.novatec.ittage.app.domain.User.class.getName() + ".authorities"); + // jhipster-needle-ehcache-add-entry + }; + } + + private void createCache(javax.cache.CacheManager cm, String cacheName) { + javax.cache.Cache cache = cm.getCache(cacheName); + if (cache != null) { + cache.clear(); + } else { + cm.createCache(cacheName, jcacheConfiguration); + } + } + + @Autowired(required = false) + public void setGitProperties(GitProperties gitProperties) { + this.gitProperties = gitProperties; + } + + @Autowired(required = false) + public void setBuildProperties(BuildProperties buildProperties) { + this.buildProperties = buildProperties; + } + + @Bean + public KeyGenerator keyGenerator() { + return new PrefixedKeyGenerator(this.gitProperties, this.buildProperties); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/Constants.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/Constants.java new file mode 100644 index 0000000..c81e7f6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/Constants.java @@ -0,0 +1,15 @@ +package de.novatec.ittage.app.config; + +/** + * Application constants. + */ +public final class Constants { + + // Regex for acceptable logins + public static final String LOGIN_REGEX = "^(?>[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)|(?>[_.@A-Za-z0-9-]+)$"; + + public static final String SYSTEM = "system"; + public static final String DEFAULT_LANGUAGE = "en"; + + private Constants() {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DatabaseConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DatabaseConfiguration.java new file mode 100644 index 0000000..d19cb35 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DatabaseConfiguration.java @@ -0,0 +1,12 @@ +package de.novatec.ittage.app.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@Configuration +@EnableJpaRepositories({ "de.novatec.ittage.app.repository" }) +@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware") +@EnableTransactionManagement +public class DatabaseConfiguration {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DateTimeFormatConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DateTimeFormatConfiguration.java new file mode 100644 index 0000000..1775846 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/DateTimeFormatConfiguration.java @@ -0,0 +1,20 @@ +package de.novatec.ittage.app.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.format.FormatterRegistry; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Configure the converters to use the ISO format for dates by default. + */ +@Configuration +public class DateTimeFormatConfiguration implements WebMvcConfigurer { + + @Override + public void addFormatters(FormatterRegistry registry) { + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(registry); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/JacksonConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/JacksonConfiguration.java new file mode 100644 index 0000000..f86566b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/JacksonConfiguration.java @@ -0,0 +1,34 @@ +package de.novatec.ittage.app.config; + +import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module; +import com.fasterxml.jackson.datatype.hibernate6.Hibernate6Module.Feature; +import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JacksonConfiguration { + + /** + * Support for Java date and time API. + * @return the corresponding Jackson module. + */ + @Bean + public JavaTimeModule javaTimeModule() { + return new JavaTimeModule(); + } + + @Bean + public Jdk8Module jdk8TimeModule() { + return new Jdk8Module(); + } + + /* + * Support for Hibernate types in Jackson. + */ + @Bean + public Hibernate6Module hibernate6Module() { + return new Hibernate6Module().configure(Feature.SERIALIZE_IDENTIFIER_FOR_LAZY_NOT_LOADED_OBJECTS, true); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LiquibaseConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LiquibaseConfiguration.java new file mode 100644 index 0000000..3ba0077 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LiquibaseConfiguration.java @@ -0,0 +1,69 @@ +package de.novatec.ittage.app.config; + +import java.util.concurrent.Executor; +import javax.sql.DataSource; +import liquibase.integration.spring.SpringLiquibase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseDataSource; +import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.env.Profiles; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.liquibase.SpringLiquibaseUtil; + +@Configuration +public class LiquibaseConfiguration { + + private final Logger log = LoggerFactory.getLogger(LiquibaseConfiguration.class); + + private final Environment env; + + public LiquibaseConfiguration(Environment env) { + this.env = env; + } + + @Bean + public SpringLiquibase liquibase( + @Qualifier("taskExecutor") Executor executor, + LiquibaseProperties liquibaseProperties, + @LiquibaseDataSource ObjectProvider liquibaseDataSource, + ObjectProvider dataSource, + DataSourceProperties dataSourceProperties + ) { + // If you don't want Liquibase to start asynchronously, substitute by this: + // SpringLiquibase liquibase = SpringLiquibaseUtil.createSpringLiquibase(liquibaseDataSource.getIfAvailable(), liquibaseProperties, dataSource.getIfUnique(), dataSourceProperties); + SpringLiquibase liquibase = SpringLiquibaseUtil.createAsyncSpringLiquibase( + this.env, + executor, + liquibaseDataSource.getIfAvailable(), + liquibaseProperties, + dataSource.getIfUnique(), + dataSourceProperties + ); + liquibase.setChangeLog("classpath:config/liquibase/master.xml"); + liquibase.setContexts(liquibaseProperties.getContexts()); + liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema()); + liquibase.setLiquibaseSchema(liquibaseProperties.getLiquibaseSchema()); + liquibase.setLiquibaseTablespace(liquibaseProperties.getLiquibaseTablespace()); + liquibase.setDatabaseChangeLogLockTable(liquibaseProperties.getDatabaseChangeLogLockTable()); + liquibase.setDatabaseChangeLogTable(liquibaseProperties.getDatabaseChangeLogTable()); + liquibase.setDropFirst(liquibaseProperties.isDropFirst()); + liquibase.setLabelFilter(liquibaseProperties.getLabelFilter()); + liquibase.setChangeLogParameters(liquibaseProperties.getParameters()); + liquibase.setRollbackFile(liquibaseProperties.getRollbackFile()); + liquibase.setTestRollbackOnUpdate(liquibaseProperties.isTestRollbackOnUpdate()); + if (env.acceptsProfiles(Profiles.of(JHipsterConstants.SPRING_PROFILE_NO_LIQUIBASE))) { + liquibase.setShouldRun(false); + } else { + liquibase.setShouldRun(liquibaseProperties.isEnabled()); + log.debug("Configuring Liquibase"); + } + return liquibase; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LocaleConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LocaleConfiguration.java new file mode 100644 index 0000000..8559491 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LocaleConfiguration.java @@ -0,0 +1,24 @@ +package de.novatec.ittage.app.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.*; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import tech.jhipster.config.locale.AngularCookieLocaleResolver; + +@Configuration +public class LocaleConfiguration implements WebMvcConfigurer { + + @Bean + public LocaleResolver localeResolver() { + return new AngularCookieLocaleResolver("NG_TRANSLATE_LANG_KEY"); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor(); + localeChangeInterceptor.setParamName("language"); + registry.addInterceptor(localeChangeInterceptor); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingAspectConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingAspectConfiguration.java new file mode 100644 index 0000000..456f10a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingAspectConfiguration.java @@ -0,0 +1,17 @@ +package de.novatec.ittage.app.config; + +import de.novatec.ittage.app.aop.logging.LoggingAspect; +import org.springframework.context.annotation.*; +import org.springframework.core.env.Environment; +import tech.jhipster.config.JHipsterConstants; + +@Configuration +@EnableAspectJAutoProxy +public class LoggingAspectConfiguration { + + @Bean + @Profile(JHipsterConstants.SPRING_PROFILE_DEVELOPMENT) + public LoggingAspect loggingAspect(Environment env) { + return new LoggingAspect(env); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingConfiguration.java new file mode 100644 index 0000000..01b9693 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/LoggingConfiguration.java @@ -0,0 +1,47 @@ +package de.novatec.ittage.app.config; + +import static tech.jhipster.config.logging.LoggingUtils.*; + +import ch.qos.logback.classic.LoggerContext; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.HashMap; +import java.util.Map; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import tech.jhipster.config.JHipsterProperties; + +/* + * Configures the console and Logstash log appenders from the app properties + */ +@Configuration +public class LoggingConfiguration { + + public LoggingConfiguration( + @Value("${spring.application.name}") String appName, + @Value("${server.port}") String serverPort, + JHipsterProperties jHipsterProperties, + ObjectMapper mapper + ) throws JsonProcessingException { + LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); + + Map map = new HashMap<>(); + map.put("app_name", appName); + map.put("app_port", serverPort); + String customFields = mapper.writeValueAsString(map); + + JHipsterProperties.Logging loggingProperties = jHipsterProperties.getLogging(); + JHipsterProperties.Logging.Logstash logstashProperties = loggingProperties.getLogstash(); + + if (loggingProperties.isUseJsonFormat()) { + addJsonConsoleAppender(context, customFields); + } + if (logstashProperties.isEnabled()) { + addLogstashTcpSocketAppender(context, customFields, logstashProperties); + } + if (loggingProperties.isUseJsonFormat() || logstashProperties.isEnabled()) { + addContextListener(context, customFields, loggingProperties); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityConfiguration.java new file mode 100644 index 0000000..213ccf7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityConfiguration.java @@ -0,0 +1,96 @@ +package de.novatec.ittage.app.config; + +import static org.springframework.security.config.Customizer.withDefaults; + +import de.novatec.ittage.app.security.*; +import de.novatec.ittage.app.web.filter.SpaWebFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; +import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; +import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter; +import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; +import tech.jhipster.config.JHipsterProperties; + +@Configuration +@EnableMethodSecurity(securedEnabled = true) +public class SecurityConfiguration { + + private final JHipsterProperties jHipsterProperties; + + public SecurityConfiguration(JHipsterProperties jHipsterProperties) { + this.jHipsterProperties = jHipsterProperties; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http, MvcRequestMatcher.Builder mvc) throws Exception { + http + .cors(withDefaults()) + .csrf(csrf -> csrf.disable()) + .addFilterAfter(new SpaWebFilter(), BasicAuthenticationFilter.class) + .headers(headers -> + headers + .contentSecurityPolicy(csp -> csp.policyDirectives(jHipsterProperties.getSecurity().getContentSecurityPolicy())) + .frameOptions(FrameOptionsConfig::sameOrigin) + .referrerPolicy(referrer -> referrer.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)) + .permissionsPolicy(permissions -> + permissions.policy( + "camera=(), fullscreen=(self), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), sync-xhr=()" + ) + ) + ) + .authorizeHttpRequests(authz -> + // prettier-ignore + authz + .requestMatchers(mvc.pattern("/index.html"), mvc.pattern("/*.js"), mvc.pattern("/*.txt"), mvc.pattern("/*.json"), mvc.pattern("/*.map"), mvc.pattern("/*.css")).permitAll() + .requestMatchers(mvc.pattern("/*.ico"), mvc.pattern("/*.png"), mvc.pattern("/*.svg"), mvc.pattern("/*.webapp")).permitAll() + .requestMatchers(mvc.pattern("/app/**")).permitAll() + .requestMatchers(mvc.pattern("/i18n/**")).permitAll() + .requestMatchers(mvc.pattern("/content/**")).permitAll() + .requestMatchers(mvc.pattern("/swagger-ui/**")).permitAll() + .requestMatchers(mvc.pattern(HttpMethod.POST, "/api/authenticate")).permitAll() + .requestMatchers(mvc.pattern(HttpMethod.GET, "/api/authenticate")).permitAll() + .requestMatchers(mvc.pattern("/api/register")).permitAll() + .requestMatchers(mvc.pattern("/api/activate")).permitAll() + .requestMatchers(mvc.pattern("/api/account/reset-password/init")).permitAll() + .requestMatchers(mvc.pattern("/api/account/reset-password/finish")).permitAll() + .requestMatchers(mvc.pattern("/api/admin/**")).hasAuthority(AuthoritiesConstants.ADMIN) + .requestMatchers(mvc.pattern("/api/**")).authenticated() + .requestMatchers(mvc.pattern("/v3/api-docs/**")).hasAuthority(AuthoritiesConstants.ADMIN) + .requestMatchers(mvc.pattern("/management/health")).permitAll() + .requestMatchers(mvc.pattern("/management/health/**")).permitAll() + .requestMatchers(mvc.pattern("/management/info")).permitAll() + .requestMatchers(mvc.pattern("/management/prometheus")).permitAll() + .requestMatchers(mvc.pattern("/management/**")).hasAuthority(AuthoritiesConstants.ADMIN) + ) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(exceptions -> + exceptions + .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint()) + .accessDeniedHandler(new BearerTokenAccessDeniedHandler()) + ) + .oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults())); + return http.build(); + } + + @Bean + MvcRequestMatcher.Builder mvc(HandlerMappingIntrospector introspector) { + return new MvcRequestMatcher.Builder(introspector); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityJwtConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityJwtConfiguration.java new file mode 100644 index 0000000..3d7007c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/SecurityJwtConfiguration.java @@ -0,0 +1,68 @@ +package de.novatec.ittage.app.config; + +import static de.novatec.ittage.app.security.SecurityUtils.AUTHORITIES_KEY; +import static de.novatec.ittage.app.security.SecurityUtils.JWT_ALGORITHM; + +import com.nimbusds.jose.jwk.source.ImmutableSecret; +import com.nimbusds.jose.util.Base64; +import de.novatec.ittage.app.management.SecurityMetersService; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +@Configuration +public class SecurityJwtConfiguration { + + @Value("${jhipster.security.authentication.jwt.base64-secret}") + private String jwtKey; + + @Bean + public JwtDecoder jwtDecoder(SecurityMetersService metersService) { + NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withSecretKey(getSecretKey()).macAlgorithm(JWT_ALGORITHM).build(); + return token -> { + try { + return jwtDecoder.decode(token); + } catch (Exception e) { + if (e.getMessage().contains("Invalid signature")) { + metersService.trackTokenInvalidSignature(); + } else if (e.getMessage().contains("Jwt expired at")) { + metersService.trackTokenExpired(); + } else if (e.getMessage().contains("Invalid JWT serialization")) { + metersService.trackTokenMalformed(); + } else if (e.getMessage().contains("Invalid unsecured/JWS/JWE")) { + metersService.trackTokenMalformed(); + } + throw e; + } + }; + } + + @Bean + public JwtEncoder jwtEncoder() { + return new NimbusJwtEncoder(new ImmutableSecret<>(getSecretKey())); + } + + @Bean + public JwtAuthenticationConverter jwtAuthenticationConverter() { + JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); + grantedAuthoritiesConverter.setAuthorityPrefix(""); + grantedAuthoritiesConverter.setAuthoritiesClaimName(AUTHORITIES_KEY); + + JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter); + return jwtAuthenticationConverter; + } + + private SecretKey getSecretKey() { + byte[] keyBytes = Base64.from(jwtKey).decode(); + return new SecretKeySpec(keyBytes, 0, keyBytes.length, JWT_ALGORITHM.getName()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/StaticResourcesWebConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/StaticResourcesWebConfiguration.java new file mode 100644 index 0000000..6857052 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/StaticResourcesWebConfiguration.java @@ -0,0 +1,59 @@ +package de.novatec.ittage.app.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.CacheControl; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.JHipsterProperties; + +@Configuration +@Profile({ JHipsterConstants.SPRING_PROFILE_PRODUCTION }) +public class StaticResourcesWebConfiguration implements WebMvcConfigurer { + + protected static final String[] RESOURCE_LOCATIONS = new String[] { + "classpath:/static/", + "classpath:/static/content/", + "classpath:/static/i18n/", + }; + protected static final String[] RESOURCE_PATHS = new String[] { + "/*.js", + "/*.css", + "/*.svg", + "/*.png", + "*.ico", + "/content/**", + "/i18n/*", + }; + + private final JHipsterProperties jhipsterProperties; + + public StaticResourcesWebConfiguration(JHipsterProperties jHipsterProperties) { + this.jhipsterProperties = jHipsterProperties; + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + ResourceHandlerRegistration resourceHandlerRegistration = appendResourceHandler(registry); + initializeResourceHandler(resourceHandlerRegistration); + } + + protected ResourceHandlerRegistration appendResourceHandler(ResourceHandlerRegistry registry) { + return registry.addResourceHandler(RESOURCE_PATHS); + } + + protected void initializeResourceHandler(ResourceHandlerRegistration resourceHandlerRegistration) { + resourceHandlerRegistration.addResourceLocations(RESOURCE_LOCATIONS).setCacheControl(getCacheControl()); + } + + protected CacheControl getCacheControl() { + return CacheControl.maxAge(getJHipsterHttpCacheProperty(), TimeUnit.DAYS).cachePublic(); + } + + private int getJHipsterHttpCacheProperty() { + return jhipsterProperties.getHttp().getCache().getTimeToLiveInDays(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/WebConfigurer.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/WebConfigurer.java new file mode 100644 index 0000000..9034858 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/WebConfigurer.java @@ -0,0 +1,96 @@ +package de.novatec.ittage.app.config; + +import static java.net.URLDecoder.decode; + +import jakarta.servlet.*; +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.nio.file.Paths; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.web.server.*; +import org.springframework.boot.web.servlet.ServletContextInitializer; +import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.util.CollectionUtils; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import tech.jhipster.config.JHipsterProperties; + +/** + * Configuration of web application with Servlet 3.0 APIs. + */ +@Configuration +public class WebConfigurer implements ServletContextInitializer, WebServerFactoryCustomizer { + + private final Logger log = LoggerFactory.getLogger(WebConfigurer.class); + + private final Environment env; + + private final JHipsterProperties jHipsterProperties; + + public WebConfigurer(Environment env, JHipsterProperties jHipsterProperties) { + this.env = env; + this.jHipsterProperties = jHipsterProperties; + } + + @Override + public void onStartup(ServletContext servletContext) throws ServletException { + if (env.getActiveProfiles().length != 0) { + log.info("Web application configuration, using profiles: {}", (Object[]) env.getActiveProfiles()); + } + + log.info("Web application fully configured"); + } + + /** + * Customize the Servlet engine: Mime types, the document root, the cache. + */ + @Override + public void customize(WebServerFactory server) { + // When running in an IDE or with ./mvnw spring-boot:run, set location of the static web assets. + setLocationForStaticAssets(server); + } + + private void setLocationForStaticAssets(WebServerFactory server) { + if (server instanceof ConfigurableServletWebServerFactory servletWebServer) { + File root; + String prefixPath = resolvePathPrefix(); + root = new File(prefixPath + "target/classes/static/"); + if (root.exists() && root.isDirectory()) { + servletWebServer.setDocumentRoot(root); + } + } + } + + /** + * Resolve path prefix to static resources. + */ + private String resolvePathPrefix() { + String fullExecutablePath = decode(this.getClass().getResource("").getPath(), StandardCharsets.UTF_8); + String rootPath = Paths.get(".").toUri().normalize().getPath(); + String extractedPath = fullExecutablePath.replace(rootPath, ""); + int extractionEndIndex = extractedPath.indexOf("target/"); + if (extractionEndIndex <= 0) { + return ""; + } + return extractedPath.substring(0, extractionEndIndex); + } + + @Bean + public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + CorsConfiguration config = jHipsterProperties.getCors(); + if (!CollectionUtils.isEmpty(config.getAllowedOrigins()) || !CollectionUtils.isEmpty(config.getAllowedOriginPatterns())) { + log.debug("Registering CORS filter"); + source.registerCorsConfiguration("/api/**", config); + source.registerCorsConfiguration("/management/**", config); + source.registerCorsConfiguration("/v3/api-docs", config); + source.registerCorsConfiguration("/swagger-ui/**", config); + } + return new CorsFilter(source); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/package-info.java new file mode 100644 index 0000000..37782b3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/config/package-info.java @@ -0,0 +1,4 @@ +/** + * Application configuration. + */ +package de.novatec.ittage.app.config; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/AbstractAuditingEntity.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/AbstractAuditingEntity.java new file mode 100644 index 0000000..a05d19b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/AbstractAuditingEntity.java @@ -0,0 +1,75 @@ +package de.novatec.ittage.app.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import java.io.Serializable; +import java.time.Instant; +import org.springframework.data.annotation.CreatedBy; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedBy; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +/** + * Base abstract class for entities which will hold definitions for created, last modified, created by, + * last modified by attributes. + */ +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@JsonIgnoreProperties(value = { "createdBy", "createdDate", "lastModifiedBy", "lastModifiedDate" }, allowGetters = true) +public abstract class AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + public abstract T getId(); + + @CreatedBy + @Column(name = "created_by", nullable = false, length = 50, updatable = false) + private String createdBy; + + @CreatedDate + @Column(name = "created_date", updatable = false) + private Instant createdDate = Instant.now(); + + @LastModifiedBy + @Column(name = "last_modified_by", length = 50) + private String lastModifiedBy; + + @LastModifiedDate + @Column(name = "last_modified_date") + private Instant lastModifiedDate = Instant.now(); + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/Authority.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/Authority.java new file mode 100644 index 0000000..328a6fe --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/Authority.java @@ -0,0 +1,61 @@ +package de.novatec.ittage.app.domain; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.io.Serializable; +import java.util.Objects; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * An authority (a security role) used by Spring Security. + */ +@Entity +@Table(name = "jhi_authority") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class Authority implements Serializable { + + private static final long serialVersionUID = 1L; + + @NotNull + @Size(max = 50) + @Id + @Column(length = 50) + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Authority)) { + return false; + } + return Objects.equals(name, ((Authority) o).name); + } + + @Override + public int hashCode() { + return Objects.hashCode(name); + } + + // prettier-ignore + @Override + public String toString() { + return "Authority{" + + "name='" + name + '\'' + + "}"; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/User.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/User.java new file mode 100644 index 0000000..1e9f274 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/User.java @@ -0,0 +1,231 @@ +package de.novatec.ittage.app.domain; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import de.novatec.ittage.app.config.Constants; +import jakarta.persistence.*; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import java.io.Serializable; +import java.time.Instant; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; + +/** + * A user. + */ +@Entity +@Table(name = "jhi_user") +@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) +public class User extends AbstractAuditingEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @NotNull + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + @Column(length = 50, unique = true, nullable = false) + private String login; + + @JsonIgnore + @NotNull + @Size(min = 60, max = 60) + @Column(name = "password_hash", length = 60, nullable = false) + private String password; + + @Size(max = 50) + @Column(name = "first_name", length = 50) + private String firstName; + + @Size(max = 50) + @Column(name = "last_name", length = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + @Column(length = 254, unique = true) + private String email; + + @NotNull + @Column(nullable = false) + private boolean activated = false; + + @Size(min = 2, max = 10) + @Column(name = "lang_key", length = 10) + private String langKey; + + @Size(max = 256) + @Column(name = "image_url", length = 256) + private String imageUrl; + + @Size(max = 20) + @Column(name = "activation_key", length = 20) + @JsonIgnore + private String activationKey; + + @Size(max = 20) + @Column(name = "reset_key", length = 20) + @JsonIgnore + private String resetKey; + + @Column(name = "reset_date") + private Instant resetDate = null; + + @JsonIgnore + @ManyToMany + @JoinTable( + name = "jhi_user_authority", + joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id") }, + inverseJoinColumns = { @JoinColumn(name = "authority_name", referencedColumnName = "name") } + ) + @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) + @BatchSize(size = 20) + private Set authorities = new HashSet<>(); + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + // Lowercase the login before saving it in database + public void setLogin(String login) { + this.login = StringUtils.lowerCase(login, Locale.ENGLISH); + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getActivationKey() { + return activationKey; + } + + public void setActivationKey(String activationKey) { + this.activationKey = activationKey; + } + + public String getResetKey() { + return resetKey; + } + + public void setResetKey(String resetKey) { + this.resetKey = resetKey; + } + + public Instant getResetDate() { + return resetDate; + } + + public void setResetDate(Instant resetDate) { + this.resetDate = resetDate; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof User)) { + return false; + } + return id != null && id.equals(((User) o).id); + } + + @Override + public int hashCode() { + // see https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/ + return getClass().hashCode(); + } + + // prettier-ignore + @Override + public String toString() { + return "User{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated='" + activated + '\'' + + ", langKey='" + langKey + '\'' + + ", activationKey='" + activationKey + '\'' + + "}"; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/package-info.java new file mode 100644 index 0000000..4df6caf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/domain/package-info.java @@ -0,0 +1,4 @@ +/** + * Domain objects. + */ +package de.novatec.ittage.app.domain; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/SecurityMetersService.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/SecurityMetersService.java new file mode 100644 index 0000000..d06bfad --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/SecurityMetersService.java @@ -0,0 +1,51 @@ +package de.novatec.ittage.app.management; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import org.springframework.stereotype.Service; + +@Service +public class SecurityMetersService { + + public static final String INVALID_TOKENS_METER_NAME = "security.authentication.invalid-tokens"; + public static final String INVALID_TOKENS_METER_DESCRIPTION = + "Indicates validation error count of the tokens presented by the clients."; + public static final String INVALID_TOKENS_METER_BASE_UNIT = "errors"; + public static final String INVALID_TOKENS_METER_CAUSE_DIMENSION = "cause"; + + private final Counter tokenInvalidSignatureCounter; + private final Counter tokenExpiredCounter; + private final Counter tokenUnsupportedCounter; + private final Counter tokenMalformedCounter; + + public SecurityMetersService(MeterRegistry registry) { + this.tokenInvalidSignatureCounter = invalidTokensCounterForCauseBuilder("invalid-signature").register(registry); + this.tokenExpiredCounter = invalidTokensCounterForCauseBuilder("expired").register(registry); + this.tokenUnsupportedCounter = invalidTokensCounterForCauseBuilder("unsupported").register(registry); + this.tokenMalformedCounter = invalidTokensCounterForCauseBuilder("malformed").register(registry); + } + + private Counter.Builder invalidTokensCounterForCauseBuilder(String cause) { + return Counter + .builder(INVALID_TOKENS_METER_NAME) + .baseUnit(INVALID_TOKENS_METER_BASE_UNIT) + .description(INVALID_TOKENS_METER_DESCRIPTION) + .tag(INVALID_TOKENS_METER_CAUSE_DIMENSION, cause); + } + + public void trackTokenInvalidSignature() { + this.tokenInvalidSignatureCounter.increment(); + } + + public void trackTokenExpired() { + this.tokenExpiredCounter.increment(); + } + + public void trackTokenUnsupported() { + this.tokenUnsupportedCounter.increment(); + } + + public void trackTokenMalformed() { + this.tokenMalformedCounter.increment(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/package-info.java new file mode 100644 index 0000000..f491eb0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/management/package-info.java @@ -0,0 +1,4 @@ +/** + * Application management. + */ +package de.novatec.ittage.app.management; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/package-info.java new file mode 100644 index 0000000..373c56c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/package-info.java @@ -0,0 +1,4 @@ +/** + * Application root. + */ +package de.novatec.ittage.app; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/AuthorityRepository.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/AuthorityRepository.java new file mode 100644 index 0000000..65a2b2c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/AuthorityRepository.java @@ -0,0 +1,9 @@ +package de.novatec.ittage.app.repository; + +import de.novatec.ittage.app.domain.Authority; +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * Spring Data JPA repository for the {@link Authority} entity. + */ +public interface AuthorityRepository extends JpaRepository {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/UserRepository.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/UserRepository.java new file mode 100644 index 0000000..884e376 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/UserRepository.java @@ -0,0 +1,36 @@ +package de.novatec.ittage.app.repository; + +import de.novatec.ittage.app.domain.User; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.data.domain.*; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Spring Data JPA repository for the {@link User} entity. + */ +@Repository +public interface UserRepository extends JpaRepository { + String USERS_BY_LOGIN_CACHE = "usersByLogin"; + + String USERS_BY_EMAIL_CACHE = "usersByEmail"; + Optional findOneByActivationKey(String activationKey); + List findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant dateTime); + Optional findOneByResetKey(String resetKey); + Optional findOneByEmailIgnoreCase(String email); + Optional findOneByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_LOGIN_CACHE) + Optional findOneWithAuthoritiesByLogin(String login); + + @EntityGraph(attributePaths = "authorities") + @Cacheable(cacheNames = USERS_BY_EMAIL_CACHE) + Optional findOneWithAuthoritiesByEmailIgnoreCase(String email); + + Page findAllByIdNotNullAndActivatedIsTrue(Pageable pageable); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/package-info.java new file mode 100644 index 0000000..afa82f5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/repository/package-info.java @@ -0,0 +1,4 @@ +/** + * Repository layer. + */ +package de.novatec.ittage.app.repository; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/AuthoritiesConstants.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/AuthoritiesConstants.java new file mode 100644 index 0000000..d3d3e6a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/AuthoritiesConstants.java @@ -0,0 +1,15 @@ +package de.novatec.ittage.app.security; + +/** + * Constants for Spring Security authorities. + */ +public final class AuthoritiesConstants { + + public static final String ADMIN = "ROLE_ADMIN"; + + public static final String USER = "ROLE_USER"; + + public static final String ANONYMOUS = "ROLE_ANONYMOUS"; + + private AuthoritiesConstants() {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/DomainUserDetailsService.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/DomainUserDetailsService.java new file mode 100644 index 0000000..4b4adfc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/DomainUserDetailsService.java @@ -0,0 +1,62 @@ +package de.novatec.ittage.app.security; + +import de.novatec.ittage.app.domain.Authority; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import java.util.*; +import org.hibernate.validator.internal.constraintvalidators.hv.EmailValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Authenticate a user from the database. + */ +@Component("userDetailsService") +public class DomainUserDetailsService implements UserDetailsService { + + private final Logger log = LoggerFactory.getLogger(DomainUserDetailsService.class); + + private final UserRepository userRepository; + + public DomainUserDetailsService(UserRepository userRepository) { + this.userRepository = userRepository; + } + + @Override + @Transactional(readOnly = true) + public UserDetails loadUserByUsername(final String login) { + log.debug("Authenticating {}", login); + + if (new EmailValidator().isValid(login, null)) { + return userRepository + .findOneWithAuthoritiesByEmailIgnoreCase(login) + .map(user -> createSpringSecurityUser(login, user)) + .orElseThrow(() -> new UsernameNotFoundException("User with email " + login + " was not found in the database")); + } + + String lowercaseLogin = login.toLowerCase(Locale.ENGLISH); + return userRepository + .findOneWithAuthoritiesByLogin(lowercaseLogin) + .map(user -> createSpringSecurityUser(lowercaseLogin, user)) + .orElseThrow(() -> new UsernameNotFoundException("User " + lowercaseLogin + " was not found in the database")); + } + + private org.springframework.security.core.userdetails.User createSpringSecurityUser(String lowercaseLogin, User user) { + if (!user.isActivated()) { + throw new UserNotActivatedException("User " + lowercaseLogin + " was not activated"); + } + List grantedAuthorities = user + .getAuthorities() + .stream() + .map(Authority::getName) + .map(SimpleGrantedAuthority::new) + .toList(); + return new org.springframework.security.core.userdetails.User(user.getLogin(), user.getPassword(), grantedAuthorities); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SecurityUtils.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SecurityUtils.java new file mode 100644 index 0000000..c3901a6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SecurityUtils.java @@ -0,0 +1,107 @@ +package de.novatec.ittage.app.security; + +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.oauth2.jose.jws.MacAlgorithm; +import org.springframework.security.oauth2.jwt.Jwt; + +/** + * Utility class for Spring Security. + */ +public final class SecurityUtils { + + public static final MacAlgorithm JWT_ALGORITHM = MacAlgorithm.HS512; + + public static final String AUTHORITIES_KEY = "auth"; + + private SecurityUtils() {} + + /** + * Get the login of the current user. + * + * @return the login of the current user. + */ + public static Optional getCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional.ofNullable(extractPrincipal(securityContext.getAuthentication())); + } + + private static String extractPrincipal(Authentication authentication) { + if (authentication == null) { + return null; + } else if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) { + return springSecurityUser.getUsername(); + } else if (authentication.getPrincipal() instanceof Jwt jwt) { + return jwt.getSubject(); + } else if (authentication.getPrincipal() instanceof String s) { + return s; + } + return null; + } + + /** + * Get the JWT of the current user. + * + * @return the JWT of the current user. + */ + public static Optional getCurrentUserJWT() { + SecurityContext securityContext = SecurityContextHolder.getContext(); + return Optional + .ofNullable(securityContext.getAuthentication()) + .filter(authentication -> authentication.getCredentials() instanceof String) + .map(authentication -> (String) authentication.getCredentials()); + } + + /** + * Check if a user is authenticated. + * + * @return true if the user is authenticated, false otherwise. + */ + public static boolean isAuthenticated() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return authentication != null && getAuthorities(authentication).noneMatch(AuthoritiesConstants.ANONYMOUS::equals); + } + + /** + * Checks if the current user has any of the authorities. + * + * @param authorities the authorities to check. + * @return true if the current user has any of the authorities, false otherwise. + */ + public static boolean hasCurrentUserAnyOfAuthorities(String... authorities) { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + return ( + authentication != null && getAuthorities(authentication).anyMatch(authority -> Arrays.asList(authorities).contains(authority)) + ); + } + + /** + * Checks if the current user has none of the authorities. + * + * @param authorities the authorities to check. + * @return true if the current user has none of the authorities, false otherwise. + */ + public static boolean hasCurrentUserNoneOfAuthorities(String... authorities) { + return !hasCurrentUserAnyOfAuthorities(authorities); + } + + /** + * Checks if the current user has a specific authority. + * + * @param authority the authority to check. + * @return true if the current user has the authority, false otherwise. + */ + public static boolean hasCurrentUserThisAuthority(String authority) { + return hasCurrentUserAnyOfAuthorities(authority); + } + + private static Stream getAuthorities(Authentication authentication) { + return authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SpringSecurityAuditorAware.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SpringSecurityAuditorAware.java new file mode 100644 index 0000000..f15f4de --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/SpringSecurityAuditorAware.java @@ -0,0 +1,18 @@ +package de.novatec.ittage.app.security; + +import de.novatec.ittage.app.config.Constants; +import java.util.Optional; +import org.springframework.data.domain.AuditorAware; +import org.springframework.stereotype.Component; + +/** + * Implementation of {@link AuditorAware} based on Spring Security. + */ +@Component +public class SpringSecurityAuditorAware implements AuditorAware { + + @Override + public Optional getCurrentAuditor() { + return Optional.of(SecurityUtils.getCurrentUserLogin().orElse(Constants.SYSTEM)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/UserNotActivatedException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/UserNotActivatedException.java new file mode 100644 index 0000000..d7d7943 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/UserNotActivatedException.java @@ -0,0 +1,19 @@ +package de.novatec.ittage.app.security; + +import org.springframework.security.core.AuthenticationException; + +/** + * This exception is thrown in case of a not activated user trying to authenticate. + */ +public class UserNotActivatedException extends AuthenticationException { + + private static final long serialVersionUID = 1L; + + public UserNotActivatedException(String message) { + super(message); + } + + public UserNotActivatedException(String message, Throwable t) { + super(message, t); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/package-info.java new file mode 100644 index 0000000..f02a4df --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/security/package-info.java @@ -0,0 +1,4 @@ +/** + * Application security utilities. + */ +package de.novatec.ittage.app.security; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/EmailAlreadyUsedException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/EmailAlreadyUsedException.java new file mode 100644 index 0000000..2fcf879 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/EmailAlreadyUsedException.java @@ -0,0 +1,10 @@ +package de.novatec.ittage.app.service; + +public class EmailAlreadyUsedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super("Email is already in use!"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/InvalidPasswordException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/InvalidPasswordException.java new file mode 100644 index 0000000..b6c54c3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/InvalidPasswordException.java @@ -0,0 +1,10 @@ +package de.novatec.ittage.app.service; + +public class InvalidPasswordException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super("Incorrect password"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/MailService.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/MailService.java new file mode 100644 index 0000000..d7f3950 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/MailService.java @@ -0,0 +1,118 @@ +package de.novatec.ittage.app.service; + +import de.novatec.ittage.app.domain.User; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.MessageSource; +import org.springframework.context.annotation.Lazy; +import org.springframework.mail.MailException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; +import tech.jhipster.config.JHipsterProperties; + +/** + * Service for sending emails. + *

+ * We use the {@link Async} annotation to send emails asynchronously. + */ +@Service +public class MailService { + + private final Logger log = LoggerFactory.getLogger(MailService.class); + + private static final String USER = "user"; + + private static final String BASE_URL = "baseUrl"; + + private final JHipsterProperties jHipsterProperties; + + private final JavaMailSender javaMailSender; + + private final MessageSource messageSource; + + private final SpringTemplateEngine templateEngine; + + @Autowired + @Lazy + private MailService self; + + public MailService( + JHipsterProperties jHipsterProperties, + JavaMailSender javaMailSender, + MessageSource messageSource, + SpringTemplateEngine templateEngine + ) { + this.jHipsterProperties = jHipsterProperties; + this.javaMailSender = javaMailSender; + this.messageSource = messageSource; + this.templateEngine = templateEngine; + } + + @Async + public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) { + log.debug( + "Send email[multipart '{}' and html '{}'] to '{}' with subject '{}' and content={}", + isMultipart, + isHtml, + to, + subject, + content + ); + + // Prepare message using a Spring helper + MimeMessage mimeMessage = javaMailSender.createMimeMessage(); + try { + MimeMessageHelper message = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name()); + message.setTo(to); + message.setFrom(jHipsterProperties.getMail().getFrom()); + message.setSubject(subject); + message.setText(content, isHtml); + javaMailSender.send(mimeMessage); + log.debug("Sent email to User '{}'", to); + } catch (MailException | MessagingException e) { + log.warn("Email could not be sent to user '{}'", to, e); + } + } + + @Async + public void sendEmailFromTemplate(User user, String templateName, String titleKey) { + if (user.getEmail() == null) { + log.debug("Email doesn't exist for user '{}'", user.getLogin()); + return; + } + Locale locale = Locale.forLanguageTag(user.getLangKey()); + Context context = new Context(locale); + context.setVariable(USER, user); + context.setVariable(BASE_URL, jHipsterProperties.getMail().getBaseUrl()); + String content = templateEngine.process(templateName, context); + String subject = messageSource.getMessage(titleKey, null, locale); + self.sendEmail(user.getEmail(), subject, content, false, true); + } + + @Async + public void sendActivationEmail(User user) { + log.debug("Sending activation email to '{}'", user.getEmail()); + self.sendEmailFromTemplate(user, "mail/activationEmail", "email.activation.title"); + } + + @Async + public void sendCreationEmail(User user) { + log.debug("Sending creation email to '{}'", user.getEmail()); + self.sendEmailFromTemplate(user, "mail/creationEmail", "email.activation.title"); + } + + @Async + public void sendPasswordResetMail(User user) { + log.debug("Sending password reset email to '{}'", user.getEmail()); + self.sendEmailFromTemplate(user, "mail/passwordResetEmail", "email.reset.title"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UserService.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UserService.java new file mode 100644 index 0000000..c202b94 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UserService.java @@ -0,0 +1,327 @@ +package de.novatec.ittage.app.service; + +import de.novatec.ittage.app.config.Constants; +import de.novatec.ittage.app.domain.Authority; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.AuthorityRepository; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import de.novatec.ittage.app.security.SecurityUtils; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.dto.UserDTO; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.cache.CacheManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import tech.jhipster.security.RandomUtil; + +/** + * Service class for managing users. + */ +@Service +@Transactional +public class UserService { + + private final Logger log = LoggerFactory.getLogger(UserService.class); + + private final UserRepository userRepository; + + private final PasswordEncoder passwordEncoder; + + private final AuthorityRepository authorityRepository; + + private final CacheManager cacheManager; + + public UserService( + UserRepository userRepository, + PasswordEncoder passwordEncoder, + AuthorityRepository authorityRepository, + CacheManager cacheManager + ) { + this.userRepository = userRepository; + this.passwordEncoder = passwordEncoder; + this.authorityRepository = authorityRepository; + this.cacheManager = cacheManager; + } + + public Optional activateRegistration(String key) { + log.debug("Activating user for activation key {}", key); + return userRepository + .findOneByActivationKey(key) + .map(user -> { + // activate given user for the registration key. + user.setActivated(true); + user.setActivationKey(null); + this.clearUserCaches(user); + log.debug("Activated user: {}", user); + return user; + }); + } + + public Optional completePasswordReset(String newPassword, String key) { + log.debug("Reset user password for reset key {}", key); + return userRepository + .findOneByResetKey(key) + .filter(user -> user.getResetDate().isAfter(Instant.now().minus(1, ChronoUnit.DAYS))) + .map(user -> { + user.setPassword(passwordEncoder.encode(newPassword)); + user.setResetKey(null); + user.setResetDate(null); + this.clearUserCaches(user); + return user; + }); + } + + public Optional requestPasswordReset(String mail) { + return userRepository + .findOneByEmailIgnoreCase(mail) + .filter(User::isActivated) + .map(user -> { + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(Instant.now()); + this.clearUserCaches(user); + return user; + }); + } + + public User registerUser(AdminUserDTO userDTO, String password) { + userRepository + .findOneByLogin(userDTO.getLogin().toLowerCase()) + .ifPresent(existingUser -> { + boolean removed = removeNonActivatedUser(existingUser); + if (!removed) { + throw new UsernameAlreadyUsedException(); + } + }); + userRepository + .findOneByEmailIgnoreCase(userDTO.getEmail()) + .ifPresent(existingUser -> { + boolean removed = removeNonActivatedUser(existingUser); + if (!removed) { + throw new EmailAlreadyUsedException(); + } + }); + User newUser = new User(); + String encryptedPassword = passwordEncoder.encode(password); + newUser.setLogin(userDTO.getLogin().toLowerCase()); + // new user gets initially a generated password + newUser.setPassword(encryptedPassword); + newUser.setFirstName(userDTO.getFirstName()); + newUser.setLastName(userDTO.getLastName()); + if (userDTO.getEmail() != null) { + newUser.setEmail(userDTO.getEmail().toLowerCase()); + } + newUser.setImageUrl(userDTO.getImageUrl()); + newUser.setLangKey(userDTO.getLangKey()); + // new user is not active + newUser.setActivated(false); + // new user gets registration key + newUser.setActivationKey(RandomUtil.generateActivationKey()); + Set authorities = new HashSet<>(); + authorityRepository.findById(AuthoritiesConstants.USER).ifPresent(authorities::add); + newUser.setAuthorities(authorities); + userRepository.save(newUser); + this.clearUserCaches(newUser); + log.debug("Created Information for User: {}", newUser); + return newUser; + } + + private boolean removeNonActivatedUser(User existingUser) { + if (existingUser.isActivated()) { + return false; + } + userRepository.delete(existingUser); + userRepository.flush(); + this.clearUserCaches(existingUser); + return true; + } + + public User createUser(AdminUserDTO userDTO) { + User user = new User(); + user.setLogin(userDTO.getLogin().toLowerCase()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + if (userDTO.getEmail() != null) { + user.setEmail(userDTO.getEmail().toLowerCase()); + } + user.setImageUrl(userDTO.getImageUrl()); + if (userDTO.getLangKey() == null) { + user.setLangKey(Constants.DEFAULT_LANGUAGE); // default language + } else { + user.setLangKey(userDTO.getLangKey()); + } + String encryptedPassword = passwordEncoder.encode(RandomUtil.generatePassword()); + user.setPassword(encryptedPassword); + user.setResetKey(RandomUtil.generateResetKey()); + user.setResetDate(Instant.now()); + user.setActivated(true); + if (userDTO.getAuthorities() != null) { + Set authorities = userDTO + .getAuthorities() + .stream() + .map(authorityRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toSet()); + user.setAuthorities(authorities); + } + userRepository.save(user); + this.clearUserCaches(user); + log.debug("Created Information for User: {}", user); + return user; + } + + /** + * Update all information for a specific user, and return the modified user. + * + * @param userDTO user to update. + * @return updated user. + */ + public Optional updateUser(AdminUserDTO userDTO) { + return Optional + .of(userRepository.findById(userDTO.getId())) + .filter(Optional::isPresent) + .map(Optional::get) + .map(user -> { + this.clearUserCaches(user); + user.setLogin(userDTO.getLogin().toLowerCase()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + if (userDTO.getEmail() != null) { + user.setEmail(userDTO.getEmail().toLowerCase()); + } + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set managedAuthorities = user.getAuthorities(); + managedAuthorities.clear(); + userDTO + .getAuthorities() + .stream() + .map(authorityRepository::findById) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(managedAuthorities::add); + userRepository.save(user); + this.clearUserCaches(user); + log.debug("Changed Information for User: {}", user); + return user; + }) + .map(AdminUserDTO::new); + } + + public void deleteUser(String login) { + userRepository + .findOneByLogin(login) + .ifPresent(user -> { + userRepository.delete(user); + this.clearUserCaches(user); + log.debug("Deleted User: {}", user); + }); + } + + /** + * Update basic information (first name, last name, email, language) for the current user. + * + * @param firstName first name of user. + * @param lastName last name of user. + * @param email email id of user. + * @param langKey language key. + * @param imageUrl image URL of user. + */ + public void updateUser(String firstName, String lastName, String email, String langKey, String imageUrl) { + SecurityUtils + .getCurrentUserLogin() + .flatMap(userRepository::findOneByLogin) + .ifPresent(user -> { + user.setFirstName(firstName); + user.setLastName(lastName); + if (email != null) { + user.setEmail(email.toLowerCase()); + } + user.setLangKey(langKey); + user.setImageUrl(imageUrl); + userRepository.save(user); + this.clearUserCaches(user); + log.debug("Changed Information for User: {}", user); + }); + } + + @Transactional + public void changePassword(String currentClearTextPassword, String newPassword) { + SecurityUtils + .getCurrentUserLogin() + .flatMap(userRepository::findOneByLogin) + .ifPresent(user -> { + String currentEncryptedPassword = user.getPassword(); + if (!passwordEncoder.matches(currentClearTextPassword, currentEncryptedPassword)) { + throw new InvalidPasswordException(); + } + String encryptedPassword = passwordEncoder.encode(newPassword); + user.setPassword(encryptedPassword); + this.clearUserCaches(user); + log.debug("Changed password for User: {}", user); + }); + } + + @Transactional(readOnly = true) + public Page getAllManagedUsers(Pageable pageable) { + return userRepository.findAll(pageable).map(AdminUserDTO::new); + } + + @Transactional(readOnly = true) + public Page getAllPublicUsers(Pageable pageable) { + return userRepository.findAllByIdNotNullAndActivatedIsTrue(pageable).map(UserDTO::new); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthoritiesByLogin(String login) { + return userRepository.findOneWithAuthoritiesByLogin(login); + } + + @Transactional(readOnly = true) + public Optional getUserWithAuthorities() { + return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin); + } + + /** + * Not activated users should be automatically deleted after 3 days. + *

+ * This is scheduled to get fired everyday, at 01:00 (am). + */ + @Scheduled(cron = "0 0 1 * * ?") + public void removeNotActivatedUsers() { + userRepository + .findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(Instant.now().minus(3, ChronoUnit.DAYS)) + .forEach(user -> { + log.debug("Deleting not activated user {}", user.getLogin()); + userRepository.delete(user); + this.clearUserCaches(user); + }); + } + + /** + * Gets a list of all the authorities. + * @return a list of all the authorities. + */ + @Transactional(readOnly = true) + public List getAuthorities() { + return authorityRepository.findAll().stream().map(Authority::getName).toList(); + } + + private void clearUserCaches(User user) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE)).evict(user.getLogin()); + if (user.getEmail() != null) { + Objects.requireNonNull(cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE)).evict(user.getEmail()); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UsernameAlreadyUsedException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UsernameAlreadyUsedException.java new file mode 100644 index 0000000..231048e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/UsernameAlreadyUsedException.java @@ -0,0 +1,10 @@ +package de.novatec.ittage.app.service; + +public class UsernameAlreadyUsedException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public UsernameAlreadyUsedException() { + super("Login name already used!"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/AdminUserDTO.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/AdminUserDTO.java new file mode 100644 index 0000000..3fd4afc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/AdminUserDTO.java @@ -0,0 +1,196 @@ +package de.novatec.ittage.app.service.dto; + +import de.novatec.ittage.app.config.Constants; +import de.novatec.ittage.app.domain.Authority; +import de.novatec.ittage.app.domain.User; +import jakarta.validation.constraints.*; +import java.io.Serializable; +import java.time.Instant; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * A DTO representing a user, with his authorities. + */ +public class AdminUserDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long id; + + @NotBlank + @Pattern(regexp = Constants.LOGIN_REGEX) + @Size(min = 1, max = 50) + private String login; + + @Size(max = 50) + private String firstName; + + @Size(max = 50) + private String lastName; + + @Email + @Size(min = 5, max = 254) + private String email; + + @Size(max = 256) + private String imageUrl; + + private boolean activated = false; + + @Size(min = 2, max = 10) + private String langKey; + + private String createdBy; + + private Instant createdDate; + + private String lastModifiedBy; + + private Instant lastModifiedDate; + + private Set authorities; + + public AdminUserDTO() { + // Empty constructor needed for Jackson. + } + + public AdminUserDTO(User user) { + this.id = user.getId(); + this.login = user.getLogin(); + this.firstName = user.getFirstName(); + this.lastName = user.getLastName(); + this.email = user.getEmail(); + this.activated = user.isActivated(); + this.imageUrl = user.getImageUrl(); + this.langKey = user.getLangKey(); + this.createdBy = user.getCreatedBy(); + this.createdDate = user.getCreatedDate(); + this.lastModifiedBy = user.getLastModifiedBy(); + this.lastModifiedDate = user.getLastModifiedDate(); + this.authorities = user.getAuthorities().stream().map(Authority::getName).collect(Collectors.toSet()); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public String getLangKey() { + return langKey; + } + + public void setLangKey(String langKey) { + this.langKey = langKey; + } + + public String getCreatedBy() { + return createdBy; + } + + public void setCreatedBy(String createdBy) { + this.createdBy = createdBy; + } + + public Instant getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Instant createdDate) { + this.createdDate = createdDate; + } + + public String getLastModifiedBy() { + return lastModifiedBy; + } + + public void setLastModifiedBy(String lastModifiedBy) { + this.lastModifiedBy = lastModifiedBy; + } + + public Instant getLastModifiedDate() { + return lastModifiedDate; + } + + public void setLastModifiedDate(Instant lastModifiedDate) { + this.lastModifiedDate = lastModifiedDate; + } + + public Set getAuthorities() { + return authorities; + } + + public void setAuthorities(Set authorities) { + this.authorities = authorities; + } + + // prettier-ignore + @Override + public String toString() { + return "AdminUserDTO{" + + "login='" + login + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", email='" + email + '\'' + + ", imageUrl='" + imageUrl + '\'' + + ", activated=" + activated + + ", langKey='" + langKey + '\'' + + ", createdBy=" + createdBy + + ", createdDate=" + createdDate + + ", lastModifiedBy='" + lastModifiedBy + '\'' + + ", lastModifiedDate=" + lastModifiedDate + + ", authorities=" + authorities + + "}"; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/PasswordChangeDTO.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/PasswordChangeDTO.java new file mode 100644 index 0000000..4eb1206 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/PasswordChangeDTO.java @@ -0,0 +1,39 @@ +package de.novatec.ittage.app.service.dto; + +import java.io.Serializable; + +/** + * A DTO representing a password change required data - current and new password. + */ +public class PasswordChangeDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String currentPassword; + private String newPassword; + + public PasswordChangeDTO() { + // Empty constructor needed for Jackson. + } + + public PasswordChangeDTO(String currentPassword, String newPassword) { + this.currentPassword = currentPassword; + this.newPassword = newPassword; + } + + public String getCurrentPassword() { + return currentPassword; + } + + public void setCurrentPassword(String currentPassword) { + this.currentPassword = currentPassword; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/UserDTO.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/UserDTO.java new file mode 100644 index 0000000..9e85c8d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/UserDTO.java @@ -0,0 +1,51 @@ +package de.novatec.ittage.app.service.dto; + +import de.novatec.ittage.app.domain.User; +import java.io.Serializable; + +/** + * A DTO representing a user, with only the public attributes. + */ +public class UserDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private Long id; + + private String login; + + public UserDTO() { + // Empty constructor needed for Jackson. + } + + public UserDTO(User user) { + this.id = user.getId(); + // Customize it here if you need, or not, firstName/lastName/etc + this.login = user.getLogin(); + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getLogin() { + return login; + } + + public void setLogin(String login) { + this.login = login; + } + + // prettier-ignore + @Override + public String toString() { + return "UserDTO{" + + "id='" + id + '\'' + + ", login='" + login + '\'' + + "}"; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/package-info.java new file mode 100644 index 0000000..0fd878b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/dto/package-info.java @@ -0,0 +1,4 @@ +/** + * Data transfer objects for rest mapping. + */ +package de.novatec.ittage.app.service.dto; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/UserMapper.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/UserMapper.java new file mode 100644 index 0000000..cc46ac2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/UserMapper.java @@ -0,0 +1,147 @@ +package de.novatec.ittage.app.service.mapper; + +import de.novatec.ittage.app.domain.Authority; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.dto.UserDTO; +import java.util.*; +import java.util.stream.Collectors; +import org.mapstruct.BeanMapping; +import org.mapstruct.Mapping; +import org.mapstruct.Named; +import org.springframework.stereotype.Service; + +/** + * Mapper for the entity {@link User} and its DTO called {@link UserDTO}. + * + * Normal mappers are generated using MapStruct, this one is hand-coded as MapStruct + * support is still in beta, and requires a manual step with an IDE. + */ +@Service +public class UserMapper { + + public List usersToUserDTOs(List users) { + return users.stream().filter(Objects::nonNull).map(this::userToUserDTO).toList(); + } + + public UserDTO userToUserDTO(User user) { + return new UserDTO(user); + } + + public List usersToAdminUserDTOs(List users) { + return users.stream().filter(Objects::nonNull).map(this::userToAdminUserDTO).toList(); + } + + public AdminUserDTO userToAdminUserDTO(User user) { + return new AdminUserDTO(user); + } + + public List userDTOsToUsers(List userDTOs) { + return userDTOs.stream().filter(Objects::nonNull).map(this::userDTOToUser).toList(); + } + + public User userDTOToUser(AdminUserDTO userDTO) { + if (userDTO == null) { + return null; + } else { + User user = new User(); + user.setId(userDTO.getId()); + user.setLogin(userDTO.getLogin()); + user.setFirstName(userDTO.getFirstName()); + user.setLastName(userDTO.getLastName()); + user.setEmail(userDTO.getEmail()); + user.setImageUrl(userDTO.getImageUrl()); + user.setActivated(userDTO.isActivated()); + user.setLangKey(userDTO.getLangKey()); + Set authorities = this.authoritiesFromStrings(userDTO.getAuthorities()); + user.setAuthorities(authorities); + return user; + } + } + + private Set authoritiesFromStrings(Set authoritiesAsString) { + Set authorities = new HashSet<>(); + + if (authoritiesAsString != null) { + authorities = + authoritiesAsString + .stream() + .map(string -> { + Authority auth = new Authority(); + auth.setName(string); + return auth; + }) + .collect(Collectors.toSet()); + } + + return authorities; + } + + public User userFromId(Long id) { + if (id == null) { + return null; + } + User user = new User(); + user.setId(id); + return user; + } + + @Named("id") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + public UserDTO toDtoId(User user) { + if (user == null) { + return null; + } + UserDTO userDto = new UserDTO(); + userDto.setId(user.getId()); + return userDto; + } + + @Named("idSet") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + public Set toDtoIdSet(Set users) { + if (users == null) { + return Collections.emptySet(); + } + + Set userSet = new HashSet<>(); + for (User userEntity : users) { + userSet.add(this.toDtoId(userEntity)); + } + + return userSet; + } + + @Named("login") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + @Mapping(target = "login", source = "login") + public UserDTO toDtoLogin(User user) { + if (user == null) { + return null; + } + UserDTO userDto = new UserDTO(); + userDto.setId(user.getId()); + userDto.setLogin(user.getLogin()); + return userDto; + } + + @Named("loginSet") + @BeanMapping(ignoreByDefault = true) + @Mapping(target = "id", source = "id") + @Mapping(target = "login", source = "login") + public Set toDtoLoginSet(Set users) { + if (users == null) { + return Collections.emptySet(); + } + + Set userSet = new HashSet<>(); + for (User userEntity : users) { + userSet.add(this.toDtoLogin(userEntity)); + } + + return userSet; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/package-info.java new file mode 100644 index 0000000..9a0d5f3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/mapper/package-info.java @@ -0,0 +1,4 @@ +/** + * Data transfer objects mappers. + */ +package de.novatec.ittage.app.service.mapper; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/package-info.java new file mode 100644 index 0000000..529011d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/service/package-info.java @@ -0,0 +1,4 @@ +/** + * Service layer. + */ +package de.novatec.ittage.app.service; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/SpaWebFilter.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/SpaWebFilter.java new file mode 100644 index 0000000..ccec472 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/SpaWebFilter.java @@ -0,0 +1,33 @@ +package de.novatec.ittage.app.web.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import org.springframework.web.filter.OncePerRequestFilter; + +public class SpaWebFilter extends OncePerRequestFilter { + + /** + * Forwards any unmapped paths (except those containing a period) to the client {@code index.html}. + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + // Request URI includes the contextPath if any, removed it. + String path = request.getRequestURI().substring(request.getContextPath().length()); + if ( + !path.startsWith("/api") && + !path.startsWith("/management") && + !path.startsWith("/v3/api-docs") && + !path.contains(".") && + path.matches("/(.*)") + ) { + request.getRequestDispatcher("/index.html").forward(request, response); + return; + } + + filterChain.doFilter(request, response); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/package-info.java new file mode 100644 index 0000000..64ed5cb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/filter/package-info.java @@ -0,0 +1,4 @@ +/** + * Request chain filters. + */ +package de.novatec.ittage.app.web.filter; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AccountResource.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AccountResource.java new file mode 100644 index 0000000..118ae2e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AccountResource.java @@ -0,0 +1,181 @@ +package de.novatec.ittage.app.web.rest; + +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.SecurityUtils; +import de.novatec.ittage.app.service.MailService; +import de.novatec.ittage.app.service.UserService; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.dto.PasswordChangeDTO; +import de.novatec.ittage.app.web.rest.errors.*; +import de.novatec.ittage.app.web.rest.vm.KeyAndPasswordVM; +import de.novatec.ittage.app.web.rest.vm.ManagedUserVM; +import jakarta.validation.Valid; +import java.util.*; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +/** + * REST controller for managing the current user's account. + */ +@RestController +@RequestMapping("/api") +public class AccountResource { + + private static class AccountResourceException extends RuntimeException { + + private AccountResourceException(String message) { + super(message); + } + } + + private final Logger log = LoggerFactory.getLogger(AccountResource.class); + + private final UserRepository userRepository; + + private final UserService userService; + + private final MailService mailService; + + public AccountResource(UserRepository userRepository, UserService userService, MailService mailService) { + this.userRepository = userRepository; + this.userService = userService; + this.mailService = mailService; + } + + /** + * {@code POST /register} : register the user. + * + * @param managedUserVM the managed user View Model. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already used. + */ + @PostMapping("/register") + @ResponseStatus(HttpStatus.CREATED) + public void registerAccount(@Valid @RequestBody ManagedUserVM managedUserVM) { + if (isPasswordLengthInvalid(managedUserVM.getPassword())) { + throw new InvalidPasswordException(); + } + User user = userService.registerUser(managedUserVM, managedUserVM.getPassword()); + mailService.sendActivationEmail(user); + } + + /** + * {@code GET /activate} : activate the registered user. + * + * @param key the activation key. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be activated. + */ + @GetMapping("/activate") + public void activateAccount(@RequestParam(value = "key") String key) { + Optional user = userService.activateRegistration(key); + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this activation key"); + } + } + + /** + * {@code GET /account} : get the current user. + * + * @return the current user. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user couldn't be returned. + */ + @GetMapping("/account") + public AdminUserDTO getAccount() { + return userService + .getUserWithAuthorities() + .map(AdminUserDTO::new) + .orElseThrow(() -> new AccountResourceException("User could not be found")); + } + + /** + * {@code POST /account} : update the current user information. + * + * @param userDTO the current user information. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already used. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the user login wasn't found. + */ + @PostMapping("/account") + public void saveAccount(@Valid @RequestBody AdminUserDTO userDTO) { + String userLogin = SecurityUtils + .getCurrentUserLogin() + .orElseThrow(() -> new AccountResourceException("Current user login not found")); + Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.orElseThrow().getLogin().equalsIgnoreCase(userLogin))) { + throw new EmailAlreadyUsedException(); + } + Optional user = userRepository.findOneByLogin(userLogin); + if (!user.isPresent()) { + throw new AccountResourceException("User could not be found"); + } + userService.updateUser( + userDTO.getFirstName(), + userDTO.getLastName(), + userDTO.getEmail(), + userDTO.getLangKey(), + userDTO.getImageUrl() + ); + } + + /** + * {@code POST /account/change-password} : changes the current user's password. + * + * @param passwordChangeDto current and new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the new password is incorrect. + */ + @PostMapping(path = "/account/change-password") + public void changePassword(@RequestBody PasswordChangeDTO passwordChangeDto) { + if (isPasswordLengthInvalid(passwordChangeDto.getNewPassword())) { + throw new InvalidPasswordException(); + } + userService.changePassword(passwordChangeDto.getCurrentPassword(), passwordChangeDto.getNewPassword()); + } + + /** + * {@code POST /account/reset-password/init} : Send an email to reset the password of the user. + * + * @param mail the mail of the user. + */ + @PostMapping(path = "/account/reset-password/init") + public void requestPasswordReset(@RequestBody String mail) { + Optional user = userService.requestPasswordReset(mail); + if (user.isPresent()) { + mailService.sendPasswordResetMail(user.orElseThrow()); + } else { + // Pretend the request has been successful to prevent checking which emails really exist + // but log that an invalid attempt has been made + log.warn("Password reset requested for non existing mail"); + } + } + + /** + * {@code POST /account/reset-password/finish} : Finish to reset the password of the user. + * + * @param keyAndPassword the generated key and the new password. + * @throws InvalidPasswordException {@code 400 (Bad Request)} if the password is incorrect. + * @throws RuntimeException {@code 500 (Internal Server Error)} if the password could not be reset. + */ + @PostMapping(path = "/account/reset-password/finish") + public void finishPasswordReset(@RequestBody KeyAndPasswordVM keyAndPassword) { + if (isPasswordLengthInvalid(keyAndPassword.getNewPassword())) { + throw new InvalidPasswordException(); + } + Optional user = userService.completePasswordReset(keyAndPassword.getNewPassword(), keyAndPassword.getKey()); + + if (!user.isPresent()) { + throw new AccountResourceException("No user was found for this reset key"); + } + } + + private static boolean isPasswordLengthInvalid(String password) { + return ( + StringUtils.isEmpty(password) || + password.length() < ManagedUserVM.PASSWORD_MIN_LENGTH || + password.length() > ManagedUserVM.PASSWORD_MAX_LENGTH + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AuthenticateController.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AuthenticateController.java new file mode 100644 index 0000000..5318596 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/AuthenticateController.java @@ -0,0 +1,124 @@ +package de.novatec.ittage.app.web.rest; + +import static de.novatec.ittage.app.security.SecurityUtils.AUTHORITIES_KEY; +import static de.novatec.ittage.app.security.SecurityUtils.JWT_ALGORITHM; + +import com.fasterxml.jackson.annotation.JsonProperty; +import de.novatec.ittage.app.web.rest.vm.LoginVM; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.JwsHeader; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.web.bind.annotation.*; + +/** + * Controller to authenticate users. + */ +@RestController +@RequestMapping("/api") +public class AuthenticateController { + + private final Logger log = LoggerFactory.getLogger(AuthenticateController.class); + + private final JwtEncoder jwtEncoder; + + @Value("${jhipster.security.authentication.jwt.token-validity-in-seconds:0}") + private long tokenValidityInSeconds; + + @Value("${jhipster.security.authentication.jwt.token-validity-in-seconds-for-remember-me:0}") + private long tokenValidityInSecondsForRememberMe; + + private final AuthenticationManagerBuilder authenticationManagerBuilder; + + public AuthenticateController(JwtEncoder jwtEncoder, AuthenticationManagerBuilder authenticationManagerBuilder) { + this.jwtEncoder = jwtEncoder; + this.authenticationManagerBuilder = authenticationManagerBuilder; + } + + @PostMapping("/authenticate") + public ResponseEntity authorize(@Valid @RequestBody LoginVM loginVM) { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( + loginVM.getUsername(), + loginVM.getPassword() + ); + + Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + String jwt = this.createToken(authentication, loginVM.isRememberMe()); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setBearerAuth(jwt); + return new ResponseEntity<>(new JWTToken(jwt), httpHeaders, HttpStatus.OK); + } + + /** + * {@code GET /authenticate} : check if the user is authenticated, and return its login. + * + * @param request the HTTP request. + * @return the login if the user is authenticated. + */ + @GetMapping("/authenticate") + public String isAuthenticated(HttpServletRequest request) { + log.debug("REST request to check if the current user is authenticated"); + return request.getRemoteUser(); + } + + public String createToken(Authentication authentication, boolean rememberMe) { + String authorities = authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.joining(" ")); + + Instant now = Instant.now(); + Instant validity; + if (rememberMe) { + validity = now.plus(this.tokenValidityInSecondsForRememberMe, ChronoUnit.SECONDS); + } else { + validity = now.plus(this.tokenValidityInSeconds, ChronoUnit.SECONDS); + } + + // @formatter:off + JwtClaimsSet claims = JwtClaimsSet.builder() + .issuedAt(now) + .expiresAt(validity) + .subject(authentication.getName()) + .claim(AUTHORITIES_KEY, authorities) + .build(); + + JwsHeader jwsHeader = JwsHeader.with(JWT_ALGORITHM).build(); + return this.jwtEncoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); + } + + /** + * Object to return as body in JWT Authentication. + */ + static class JWTToken { + + private String idToken; + + JWTToken(String idToken) { + this.idToken = idToken; + } + + @JsonProperty("id_token") + String getIdToken() { + return idToken; + } + + void setIdToken(String idToken) { + this.idToken = idToken; + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/PublicUserResource.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/PublicUserResource.java new file mode 100644 index 0000000..d550534 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/PublicUserResource.java @@ -0,0 +1,65 @@ +package de.novatec.ittage.app.web.rest; + +import de.novatec.ittage.app.service.UserService; +import de.novatec.ittage.app.service.dto.UserDTO; +import java.util.*; +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import tech.jhipster.web.util.PaginationUtil; + +@RestController +@RequestMapping("/api") +public class PublicUserResource { + + private static final List ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( + Arrays.asList("id", "login", "firstName", "lastName", "email", "activated", "langKey") + ); + + private final Logger log = LoggerFactory.getLogger(PublicUserResource.class); + + private final UserService userService; + + public PublicUserResource(UserService userService) { + this.userService = userService; + } + + /** + * {@code GET /users} : get all users with only public information - calling this method is allowed for anyone. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + public ResponseEntity> getAllPublicUsers(@org.springdoc.core.annotations.ParameterObject Pageable pageable) { + log.debug("REST request to get all public User names"); + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build(); + } + + final Page page = userService.getAllPublicUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + private boolean onlyContainsAllowedProperties(Pageable pageable) { + return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); + } + + /** + * Gets a list of all roles. + * @return a string list of all roles. + */ + @GetMapping("/authorities") + public List getAuthorities() { + return userService.getAuthorities(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/UserResource.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/UserResource.java new file mode 100644 index 0000000..e1d4cb8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/UserResource.java @@ -0,0 +1,207 @@ +package de.novatec.ittage.app.web.rest; + +import de.novatec.ittage.app.config.Constants; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import de.novatec.ittage.app.service.MailService; +import de.novatec.ittage.app.service.UserService; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.web.rest.errors.BadRequestAlertException; +import de.novatec.ittage.app.web.rest.errors.EmailAlreadyUsedException; +import de.novatec.ittage.app.web.rest.errors.LoginAlreadyUsedException; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Pattern; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import tech.jhipster.web.util.HeaderUtil; +import tech.jhipster.web.util.PaginationUtil; +import tech.jhipster.web.util.ResponseUtil; + +/** + * REST controller for managing users. + *

+ * This class accesses the {@link de.novatec.ittage.app.domain.User} entity, and needs to fetch its collection of authorities. + *

+ * For a normal use-case, it would be better to have an eager relationship between User and Authority, + * and send everything to the client side: there would be no View Model and DTO, a lot less code, and an outer-join + * which would be good for performance. + *

+ * We use a View Model and a DTO for 3 reasons: + *

    + *
  • We want to keep a lazy association between the user and the authorities, because people will + * quite often do relationships with the user, and we don't want them to get the authorities all + * the time for nothing (for performance reasons). This is the #1 goal: we should not impact our users' + * application because of this use-case.
  • + *
  • Not having an outer join causes n+1 requests to the database. This is not a real issue as + * we have by default a second-level cache. This means on the first HTTP call we do the n+1 requests, + * but then all authorities come from the cache, so in fact it's much better than doing an outer join + * (which will get lots of data from the database, for each HTTP call).
  • + *
  • As this manages users, for security reasons, we'd rather have a DTO layer.
  • + *
+ *

+ * Another option would be to have a specific JPA entity graph to handle this case. + */ +@RestController +@RequestMapping("/api/admin") +public class UserResource { + + private static final List ALLOWED_ORDERED_PROPERTIES = Collections.unmodifiableList( + Arrays.asList( + "id", + "login", + "firstName", + "lastName", + "email", + "activated", + "langKey", + "createdBy", + "createdDate", + "lastModifiedBy", + "lastModifiedDate" + ) + ); + + private final Logger log = LoggerFactory.getLogger(UserResource.class); + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final UserService userService; + + private final UserRepository userRepository; + + private final MailService mailService; + + public UserResource(UserService userService, UserRepository userRepository, MailService mailService) { + this.userService = userService; + this.userRepository = userRepository; + this.mailService = mailService; + } + + /** + * {@code POST /admin/users} : Creates a new user. + *

+ * Creates a new user if the login and email are not already used, and sends an + * mail with an activation link. + * The user needs to be activated on creation. + * + * @param userDTO the user to create. + * @return the {@link ResponseEntity} with status {@code 201 (Created)} and with body the new user, or with status {@code 400 (Bad Request)} if the login or email is already in use. + * @throws URISyntaxException if the Location URI syntax is incorrect. + * @throws BadRequestAlertException {@code 400 (Bad Request)} if the login or email is already in use. + */ + @PostMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity createUser(@Valid @RequestBody AdminUserDTO userDTO) throws URISyntaxException { + log.debug("REST request to save User : {}", userDTO); + + if (userDTO.getId() != null) { + throw new BadRequestAlertException("A new user cannot already have an ID", "userManagement", "idexists"); + // Lowercase the user login before comparing with database + } else if (userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()).isPresent()) { + throw new LoginAlreadyUsedException(); + } else if (userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()).isPresent()) { + throw new EmailAlreadyUsedException(); + } else { + User newUser = userService.createUser(userDTO); + mailService.sendCreationEmail(newUser); + return ResponseEntity + .created(new URI("/api/admin/users/" + newUser.getLogin())) + .headers(HeaderUtil.createAlert(applicationName, "userManagement.created", newUser.getLogin())) + .body(newUser); + } + } + + /** + * {@code PUT /admin/users} : Updates an existing User. + * + * @param userDTO the user to update. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the updated user. + * @throws EmailAlreadyUsedException {@code 400 (Bad Request)} if the email is already in use. + * @throws LoginAlreadyUsedException {@code 400 (Bad Request)} if the login is already in use. + */ + @PutMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity updateUser(@Valid @RequestBody AdminUserDTO userDTO) { + log.debug("REST request to update User : {}", userDTO); + Optional existingUser = userRepository.findOneByEmailIgnoreCase(userDTO.getEmail()); + if (existingUser.isPresent() && (!existingUser.orElseThrow().getId().equals(userDTO.getId()))) { + throw new EmailAlreadyUsedException(); + } + existingUser = userRepository.findOneByLogin(userDTO.getLogin().toLowerCase()); + if (existingUser.isPresent() && (!existingUser.orElseThrow().getId().equals(userDTO.getId()))) { + throw new LoginAlreadyUsedException(); + } + Optional updatedUser = userService.updateUser(userDTO); + + return ResponseUtil.wrapOrNotFound( + updatedUser, + HeaderUtil.createAlert(applicationName, "userManagement.updated", userDTO.getLogin()) + ); + } + + /** + * {@code GET /admin/users} : get all users with all the details - calling this are only allowed for the administrators. + * + * @param pageable the pagination information. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body all users. + */ + @GetMapping("/users") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity> getAllUsers(@org.springdoc.core.annotations.ParameterObject Pageable pageable) { + log.debug("REST request to get all User for an admin"); + if (!onlyContainsAllowedProperties(pageable)) { + return ResponseEntity.badRequest().build(); + } + + final Page page = userService.getAllManagedUsers(pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page); + return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK); + } + + private boolean onlyContainsAllowedProperties(Pageable pageable) { + return pageable.getSort().stream().map(Sort.Order::getProperty).allMatch(ALLOWED_ORDERED_PROPERTIES::contains); + } + + /** + * {@code GET /admin/users/:login} : get the "login" user. + * + * @param login the login of the user to find. + * @return the {@link ResponseEntity} with status {@code 200 (OK)} and with body the "login" user, or with status {@code 404 (Not Found)}. + */ + @GetMapping("/users/{login}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity getUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { + log.debug("REST request to get User : {}", login); + return ResponseUtil.wrapOrNotFound(userService.getUserWithAuthoritiesByLogin(login).map(AdminUserDTO::new)); + } + + /** + * {@code DELETE /admin/users/:login} : delete the "login" User. + * + * @param login the login of the user to delete. + * @return the {@link ResponseEntity} with status {@code 204 (NO_CONTENT)}. + */ + @DeleteMapping("/users/{login}") + @PreAuthorize("hasAuthority(\"" + AuthoritiesConstants.ADMIN + "\")") + public ResponseEntity deleteUser(@PathVariable @Pattern(regexp = Constants.LOGIN_REGEX) String login) { + log.debug("REST request to delete User: {}", login); + userService.deleteUser(login); + return ResponseEntity.noContent().headers(HeaderUtil.createAlert(applicationName, "userManagement.deleted", login)).build(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/BadRequestAlertException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/BadRequestAlertException.java new file mode 100644 index 0000000..ada1f1b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/BadRequestAlertException.java @@ -0,0 +1,50 @@ +package de.novatec.ittage.app.web.rest.errors; + +import java.net.URI; +import org.springframework.http.HttpStatus; +import org.springframework.web.ErrorResponseException; +import tech.jhipster.web.rest.errors.ProblemDetailWithCause; +import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder; + +@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep +public class BadRequestAlertException extends ErrorResponseException { + + private static final long serialVersionUID = 1L; + + private final String entityName; + + private final String errorKey; + + public BadRequestAlertException(String defaultMessage, String entityName, String errorKey) { + this(ErrorConstants.DEFAULT_TYPE, defaultMessage, entityName, errorKey); + } + + public BadRequestAlertException(URI type, String defaultMessage, String entityName, String errorKey) { + super( + HttpStatus.BAD_REQUEST, + ProblemDetailWithCauseBuilder + .instance() + .withStatus(HttpStatus.BAD_REQUEST.value()) + .withType(type) + .withTitle(defaultMessage) + .withProperty("message", "error." + errorKey) + .withProperty("params", entityName) + .build(), + null + ); + this.entityName = entityName; + this.errorKey = errorKey; + } + + public String getEntityName() { + return entityName; + } + + public String getErrorKey() { + return errorKey; + } + + public ProblemDetailWithCause getProblemDetailWithCause() { + return (ProblemDetailWithCause) this.getBody(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/EmailAlreadyUsedException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/EmailAlreadyUsedException.java new file mode 100644 index 0000000..1b711b8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/EmailAlreadyUsedException.java @@ -0,0 +1,11 @@ +package de.novatec.ittage.app.web.rest.errors; + +@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep +public class EmailAlreadyUsedException extends BadRequestAlertException { + + private static final long serialVersionUID = 1L; + + public EmailAlreadyUsedException() { + super(ErrorConstants.EMAIL_ALREADY_USED_TYPE, "Email is already in use!", "userManagement", "emailexists"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ErrorConstants.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ErrorConstants.java new file mode 100644 index 0000000..82d070f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ErrorConstants.java @@ -0,0 +1,17 @@ +package de.novatec.ittage.app.web.rest.errors; + +import java.net.URI; + +public final class ErrorConstants { + + public static final String ERR_CONCURRENCY_FAILURE = "error.concurrencyFailure"; + public static final String ERR_VALIDATION = "error.validation"; + public static final String PROBLEM_BASE_URL = "https://www.jhipster.tech/problem"; + public static final URI DEFAULT_TYPE = URI.create(PROBLEM_BASE_URL + "/problem-with-message"); + public static final URI CONSTRAINT_VIOLATION_TYPE = URI.create(PROBLEM_BASE_URL + "/constraint-violation"); + public static final URI INVALID_PASSWORD_TYPE = URI.create(PROBLEM_BASE_URL + "/invalid-password"); + public static final URI EMAIL_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/email-already-used"); + public static final URI LOGIN_ALREADY_USED_TYPE = URI.create(PROBLEM_BASE_URL + "/login-already-used"); + + private ErrorConstants() {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslator.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslator.java new file mode 100644 index 0000000..d6eaf75 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslator.java @@ -0,0 +1,265 @@ +package de.novatec.ittage.app.web.rest.errors; + +import static org.springframework.core.annotation.AnnotatedElementUtils.findMergedAnnotation; + +import jakarta.servlet.http.HttpServletRequest; +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.env.Environment; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageConversionException; +import org.springframework.lang.Nullable; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.web.ErrorResponse; +import org.springframework.web.ErrorResponseException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.web.rest.errors.ProblemDetailWithCause; +import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder; +import tech.jhipster.web.util.HeaderUtil; + +/** + * Controller advice to translate the server side exceptions to client-friendly json structures. + * The error response follows RFC7807 - Problem Details for HTTP APIs (https://tools.ietf.org/html/rfc7807). + */ +@ControllerAdvice +public class ExceptionTranslator extends ResponseEntityExceptionHandler { + + private static final String FIELD_ERRORS_KEY = "fieldErrors"; + private static final String MESSAGE_KEY = "message"; + private static final String PATH_KEY = "path"; + private static final boolean CASUAL_CHAIN_ENABLED = false; + + @Value("${jhipster.clientApp.name}") + private String applicationName; + + private final Environment env; + + public ExceptionTranslator(Environment env) { + this.env = env; + } + + @ExceptionHandler + public ResponseEntity handleAnyException(Throwable ex, NativeWebRequest request) { + ProblemDetailWithCause pdCause = wrapAndCustomizeProblem(ex, request); + return handleExceptionInternal((Exception) ex, pdCause, buildHeaders(ex), HttpStatusCode.valueOf(pdCause.getStatus()), request); + } + + @Nullable + @Override + protected ResponseEntity handleExceptionInternal( + Exception ex, + @Nullable Object body, + HttpHeaders headers, + HttpStatusCode statusCode, + WebRequest request + ) { + body = body == null ? wrapAndCustomizeProblem((Throwable) ex, (NativeWebRequest) request) : body; + return super.handleExceptionInternal(ex, body, headers, statusCode, request); + } + + protected ProblemDetailWithCause wrapAndCustomizeProblem(Throwable ex, NativeWebRequest request) { + return customizeProblem(getProblemDetailWithCause(ex), ex, request); + } + + private ProblemDetailWithCause getProblemDetailWithCause(Throwable ex) { + if ( + ex instanceof de.novatec.ittage.app.service.UsernameAlreadyUsedException + ) return (ProblemDetailWithCause) new LoginAlreadyUsedException().getBody(); + if ( + ex instanceof de.novatec.ittage.app.service.EmailAlreadyUsedException + ) return (ProblemDetailWithCause) new EmailAlreadyUsedException().getBody(); + if ( + ex instanceof de.novatec.ittage.app.service.InvalidPasswordException + ) return (ProblemDetailWithCause) new InvalidPasswordException().getBody(); + + if ( + ex instanceof ErrorResponseException exp && exp.getBody() instanceof ProblemDetailWithCause problemDetailWithCause + ) return problemDetailWithCause; + return ProblemDetailWithCauseBuilder.instance().withStatus(toStatus(ex).value()).build(); + } + + protected ProblemDetailWithCause customizeProblem(ProblemDetailWithCause problem, Throwable err, NativeWebRequest request) { + if (problem.getStatus() <= 0) problem.setStatus(toStatus(err)); + + if (problem.getType() == null || problem.getType().equals(URI.create("about:blank"))) problem.setType(getMappedType(err)); + + // higher precedence to Custom/ResponseStatus types + String title = extractTitle(err, problem.getStatus()); + String problemTitle = problem.getTitle(); + if (problemTitle == null || !problemTitle.equals(title)) { + problem.setTitle(title); + } + + if (problem.getDetail() == null) { + // higher precedence to cause + problem.setDetail(getCustomizedErrorDetails(err)); + } + + Map problemProperties = problem.getProperties(); + if (problemProperties == null || !problemProperties.containsKey(MESSAGE_KEY)) problem.setProperty( + MESSAGE_KEY, + getMappedMessageKey(err) != null ? getMappedMessageKey(err) : "error.http." + problem.getStatus() + ); + + if (problemProperties == null || !problemProperties.containsKey(PATH_KEY)) problem.setProperty(PATH_KEY, getPathValue(request)); + + if ( + (err instanceof MethodArgumentNotValidException fieldException) && + (problemProperties == null || !problemProperties.containsKey(FIELD_ERRORS_KEY)) + ) problem.setProperty(FIELD_ERRORS_KEY, getFieldErrors(fieldException)); + + problem.setCause(buildCause(err.getCause(), request).orElse(null)); + + return problem; + } + + private String extractTitle(Throwable err, int statusCode) { + return getCustomizedTitle(err) != null ? getCustomizedTitle(err) : extractTitleForResponseStatus(err, statusCode); + } + + private List getFieldErrors(MethodArgumentNotValidException ex) { + return ex + .getBindingResult() + .getFieldErrors() + .stream() + .map(f -> + new FieldErrorVM( + f.getObjectName().replaceFirst("DTO$", ""), + f.getField(), + StringUtils.isNotBlank(f.getDefaultMessage()) ? f.getDefaultMessage() : f.getCode() + ) + ) + .toList(); + } + + private String extractTitleForResponseStatus(Throwable err, int statusCode) { + ResponseStatus specialStatus = extractResponseStatus(err); + return specialStatus == null ? HttpStatus.valueOf(statusCode).getReasonPhrase() : specialStatus.reason(); + } + + private String extractURI(NativeWebRequest request) { + HttpServletRequest nativeRequest = request.getNativeRequest(HttpServletRequest.class); + return nativeRequest != null ? nativeRequest.getRequestURI() : StringUtils.EMPTY; + } + + private HttpStatus toStatus(final Throwable throwable) { + // Let the ErrorResponse take this responsibility + if (throwable instanceof ErrorResponse err) return HttpStatus.valueOf(err.getBody().getStatus()); + + return Optional + .ofNullable(getMappedStatus(throwable)) + .orElse( + Optional.ofNullable(resolveResponseStatus(throwable)).map(ResponseStatus::value).orElse(HttpStatus.INTERNAL_SERVER_ERROR) + ); + } + + private ResponseStatus extractResponseStatus(final Throwable throwable) { + return Optional.ofNullable(resolveResponseStatus(throwable)).orElse(null); + } + + private ResponseStatus resolveResponseStatus(final Throwable type) { + final ResponseStatus candidate = findMergedAnnotation(type.getClass(), ResponseStatus.class); + return candidate == null && type.getCause() != null ? resolveResponseStatus(type.getCause()) : candidate; + } + + private URI getMappedType(Throwable err) { + if (err instanceof MethodArgumentNotValidException) return ErrorConstants.CONSTRAINT_VIOLATION_TYPE; + return ErrorConstants.DEFAULT_TYPE; + } + + private String getMappedMessageKey(Throwable err) { + if (err instanceof MethodArgumentNotValidException) { + return ErrorConstants.ERR_VALIDATION; + } else if (err instanceof ConcurrencyFailureException || err.getCause() instanceof ConcurrencyFailureException) { + return ErrorConstants.ERR_CONCURRENCY_FAILURE; + } + return null; + } + + private String getCustomizedTitle(Throwable err) { + if (err instanceof MethodArgumentNotValidException) return "Method argument not valid"; + return null; + } + + private String getCustomizedErrorDetails(Throwable err) { + Collection activeProfiles = Arrays.asList(env.getActiveProfiles()); + if (activeProfiles.contains(JHipsterConstants.SPRING_PROFILE_PRODUCTION)) { + if (err instanceof HttpMessageConversionException) return "Unable to convert http message"; + if (err instanceof DataAccessException) return "Failure during data access"; + if (containsPackageName(err.getMessage())) return "Unexpected runtime exception"; + } + return err.getCause() != null ? err.getCause().getMessage() : err.getMessage(); + } + + private HttpStatus getMappedStatus(Throwable err) { + // Where we disagree with Spring defaults + if (err instanceof AccessDeniedException) return HttpStatus.FORBIDDEN; + if (err instanceof ConcurrencyFailureException) return HttpStatus.CONFLICT; + if (err instanceof BadCredentialsException) return HttpStatus.UNAUTHORIZED; + return null; + } + + private URI getPathValue(NativeWebRequest request) { + if (request == null) return URI.create("about:blank"); + return URI.create(extractURI(request)); + } + + private HttpHeaders buildHeaders(Throwable err) { + return err instanceof BadRequestAlertException badRequestAlertException + ? HeaderUtil.createFailureAlert( + applicationName, + true, + badRequestAlertException.getEntityName(), + badRequestAlertException.getErrorKey(), + badRequestAlertException.getMessage() + ) + : null; + } + + public Optional buildCause(final Throwable throwable, NativeWebRequest request) { + if (throwable != null && isCasualChainEnabled()) { + return Optional.of(customizeProblem(getProblemDetailWithCause(throwable), throwable, request)); + } + return Optional.ofNullable(null); + } + + private boolean isCasualChainEnabled() { + // Customize as per the needs + return CASUAL_CHAIN_ENABLED; + } + + private boolean containsPackageName(String message) { + // This list is for sure not complete + return StringUtils.containsAny( + message, + "org.", + "java.", + "net.", + "jakarta.", + "javax.", + "com.", + "io.", + "de.", + "de.novatec.ittage.app" + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/FieldErrorVM.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/FieldErrorVM.java new file mode 100644 index 0000000..727e116 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/FieldErrorVM.java @@ -0,0 +1,32 @@ +package de.novatec.ittage.app.web.rest.errors; + +import java.io.Serializable; + +public class FieldErrorVM implements Serializable { + + private static final long serialVersionUID = 1L; + + private final String objectName; + + private final String field; + + private final String message; + + public FieldErrorVM(String dto, String field, String message) { + this.objectName = dto; + this.field = field; + this.message = message; + } + + public String getObjectName() { + return objectName; + } + + public String getField() { + return field; + } + + public String getMessage() { + return message; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/InvalidPasswordException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/InvalidPasswordException.java new file mode 100644 index 0000000..d9d5437 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/InvalidPasswordException.java @@ -0,0 +1,24 @@ +package de.novatec.ittage.app.web.rest.errors; + +import org.springframework.http.HttpStatus; +import org.springframework.web.ErrorResponseException; +import tech.jhipster.web.rest.errors.ProblemDetailWithCause.ProblemDetailWithCauseBuilder; + +@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep +public class InvalidPasswordException extends ErrorResponseException { + + private static final long serialVersionUID = 1L; + + public InvalidPasswordException() { + super( + HttpStatus.BAD_REQUEST, + ProblemDetailWithCauseBuilder + .instance() + .withStatus(HttpStatus.BAD_REQUEST.value()) + .withType(ErrorConstants.INVALID_PASSWORD_TYPE) + .withTitle("Incorrect password") + .build(), + null + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/LoginAlreadyUsedException.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/LoginAlreadyUsedException.java new file mode 100644 index 0000000..4004a0a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/LoginAlreadyUsedException.java @@ -0,0 +1,11 @@ +package de.novatec.ittage.app.web.rest.errors; + +@SuppressWarnings("java:S110") // Inheritance tree of classes should not be too deep +public class LoginAlreadyUsedException extends BadRequestAlertException { + + private static final long serialVersionUID = 1L; + + public LoginAlreadyUsedException() { + super(ErrorConstants.LOGIN_ALREADY_USED_TYPE, "Login name already used!", "userManagement", "userexists"); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/package-info.java new file mode 100644 index 0000000..e908edf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/errors/package-info.java @@ -0,0 +1,4 @@ +/** + * Rest layer error handling. + */ +package de.novatec.ittage.app.web.rest.errors; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/package-info.java new file mode 100644 index 0000000..ca5ea04 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/package-info.java @@ -0,0 +1,4 @@ +/** + * Rest layer. + */ +package de.novatec.ittage.app.web.rest; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/KeyAndPasswordVM.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/KeyAndPasswordVM.java new file mode 100644 index 0000000..21477b5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/KeyAndPasswordVM.java @@ -0,0 +1,27 @@ +package de.novatec.ittage.app.web.rest.vm; + +/** + * View Model object for storing the user's key and password. + */ +public class KeyAndPasswordVM { + + private String key; + + private String newPassword; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getNewPassword() { + return newPassword; + } + + public void setNewPassword(String newPassword) { + this.newPassword = newPassword; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/LoginVM.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/LoginVM.java new file mode 100644 index 0000000..527c8e3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/LoginVM.java @@ -0,0 +1,53 @@ +package de.novatec.ittage.app.web.rest.vm; + +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * View Model object for storing a user's credentials. + */ +public class LoginVM { + + @NotNull + @Size(min = 1, max = 50) + private String username; + + @NotNull + @Size(min = 4, max = 100) + private String password; + + private boolean rememberMe; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isRememberMe() { + return rememberMe; + } + + public void setRememberMe(boolean rememberMe) { + this.rememberMe = rememberMe; + } + + // prettier-ignore + @Override + public String toString() { + return "LoginVM{" + + "username='" + username + '\'' + + ", rememberMe=" + rememberMe + + '}'; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/ManagedUserVM.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/ManagedUserVM.java new file mode 100644 index 0000000..582dcb6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/ManagedUserVM.java @@ -0,0 +1,35 @@ +package de.novatec.ittage.app.web.rest.vm; + +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import jakarta.validation.constraints.Size; + +/** + * View Model extending the AdminUserDTO, which is meant to be used in the user management UI. + */ +public class ManagedUserVM extends AdminUserDTO { + + public static final int PASSWORD_MIN_LENGTH = 4; + + public static final int PASSWORD_MAX_LENGTH = 100; + + @Size(min = PASSWORD_MIN_LENGTH, max = PASSWORD_MAX_LENGTH) + private String password; + + public ManagedUserVM() { + // Empty constructor needed for Jackson. + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + // prettier-ignore + @Override + public String toString() { + return "ManagedUserVM{" + super.toString() + "} "; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/package-info.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/package-info.java new file mode 100644 index 0000000..344ab75 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/java/de/novatec/ittage/app/web/rest/vm/package-info.java @@ -0,0 +1,4 @@ +/** + * Rest layer visual models. + */ +package de.novatec.ittage.app.web.rest.vm; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/banner.txt b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/banner.txt new file mode 100644 index 0000000..5be7dbe --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/banner.txt @@ -0,0 +1,10 @@ + + ${AnsiColor.GREEN} ██╗${AnsiColor.RED} ██╗ ██╗ ████████╗ ███████╗ ██████╗ ████████╗ ████████╗ ███████╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ██║ ██║ ╚══██╔══╝ ██╔═══██╗ ██╔════╝ ╚══██╔══╝ ██╔═════╝ ██╔═══██╗ + ${AnsiColor.GREEN} ██║${AnsiColor.RED} ████████║ ██║ ███████╔╝ ╚█████╗ ██║ ██████╗ ███████╔╝ + ${AnsiColor.GREEN}██╗ ██║${AnsiColor.RED} ██╔═══██║ ██║ ██╔════╝ ╚═══██╗ ██║ ██╔═══╝ ██╔══██║ + ${AnsiColor.GREEN}╚██████╔╝${AnsiColor.RED} ██║ ██║ ████████╗ ██║ ██████╔╝ ██║ ████████╗ ██║ ╚██╗ + ${AnsiColor.GREEN} ╚═════╝ ${AnsiColor.RED} ╚═╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══════╝ ╚═╝ ╚═╝ + +${AnsiColor.BRIGHT_BLUE}:: JHipster 🤓 :: Running Spring Boot ${spring-boot.version} :: Startup profile(s) ${spring.profiles.active} :: +:: https://www.jhipster.tech ::${AnsiColor.DEFAULT} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-dev.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-dev.yml new file mode 100644 index 0000000..d893c14 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-dev.yml @@ -0,0 +1,110 @@ +# =================================================================== +# Spring Boot configuration for the "dev" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +logging: + level: + ROOT: DEBUG + tech.jhipster: DEBUG + org.hibernate.SQL: DEBUG + de.novatec.ittage.app: DEBUG + +spring: + devtools: + restart: + enabled: true + additional-exclude: static/** + livereload: + enabled: false # we use Webpack dev server + BrowserSync for livereload + jackson: + serialization: + indent-output: true + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://localhost:3306/app?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + liquibase: + # Remove 'faker' if you do not want the sample data to be loaded automatically + contexts: dev, faker + mail: + host: localhost + port: 25 + username: + password: + messages: + cache-duration: PT1S # 1 second, see the ISO 8601 standard + thymeleaf: + cache: false + +server: + port: 8080 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 100 # Number of objects in each cache entry + # CORS is only enabled by default with the "dev" profile + cors: + # Allow Ionic for JHipster by default (* no longer allowed in Spring Boot 2.4+) + allowed-origins: 'http://localhost:8100,https://localhost:8100,http://localhost:9000,https://localhost:9000,http://localhost:4200,https://localhost:4200' + # Enable CORS when running in GitHub Codespaces + allowed-origin-patterns: 'https://*.githubpreview.dev' + allowed-methods: '*' + allowed-headers: '*' + exposed-headers: 'Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params' + allow-credentials: true + max-age: 1800 + security: + authentication: + jwt: + # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) + base64-secret: MmQ3MzkwYzgzMzMxOTYwN2IxMGViYTM4MjlmNThkN2JhMzNkMDQ5ZjIyNzJkNDhlNWNkZGNhZDdjYzRiNzIyMjVlYTAwMmI5NTA3ZmIxZTQzODNhZWY5OWFjZTBiNTVmODlhMzY4NDhjMmQ1Y2UzNGMwMTEzNWFiNWU5YWEwMTY= + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + mail: # specific JHipster mail property, for standard properties see MailProperties + base-url: http://127.0.0.1:8080 + logging: + use-json-format: false # By default, logs are not in Json format + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + ring-buffer-size: 512 +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-prod.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-prod.yml new file mode 100644 index 0000000..0d69265 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-prod.yml @@ -0,0 +1,129 @@ +# =================================================================== +# Spring Boot configuration for the "prod" profile. +# +# This configuration overrides the application.yml file. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +logging: + level: + ROOT: INFO + tech.jhipster: INFO + de.novatec.ittage.app: INFO + +management: + prometheus: + metrics: + export: + enabled: false + +spring: + devtools: + restart: + enabled: false + livereload: + enabled: false + datasource: + type: com.zaxxer.hikari.HikariDataSource + url: jdbc:mysql://localhost:3306/app?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true + username: root + password: + hikari: + poolName: Hikari + auto-commit: false + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + # Replace by 'prod, faker' to add the faker context and have sample data loaded in production + liquibase: + contexts: prod + mail: + host: localhost + port: 25 + username: + password: + thymeleaf: + cache: true + +# =================================================================== +# To enable TLS in production, generate a certificate using: +# keytool -genkey -alias app -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 3650 +# +# You can also use Let's Encrypt: +# See details in topic "Create a Java Keystore (.JKS) from Let's Encrypt Certificates" on https://maximilian-boehm.com/en-gb/blog +# +# Then, modify the server.ssl properties so your "server" configuration looks like: +# +# server: +# port: 443 +# ssl: +# key-store: classpath:config/tls/keystore.p12 +# key-store-password: password +# key-store-type: PKCS12 +# key-alias: selfsigned +# # The ciphers suite enforce the security by deactivating some old and deprecated SSL cipher, this list was tested against SSL Labs (https://www.ssllabs.com/ssltest/) +# ciphers: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 ,TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 ,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_DHE_RSA_WITH_AES_128_CBC_SHA256,TLS_DHE_RSA_WITH_AES_128_CBC_SHA,TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,TLS_DHE_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_RSA_WITH_CAMELLIA_256_CBC_SHA,TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA,TLS_RSA_WITH_CAMELLIA_128_CBC_SHA +# =================================================================== +server: + port: 8080 + shutdown: graceful # see https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-graceful-shutdown + compression: + enabled: true + mime-types: text/html,text/xml,text/plain,text/css,application/javascript,application/json,image/svg+xml + min-response-size: 1024 + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + http: + cache: # Used by the CachingHttpHeadersFilter + timeToLiveInDays: 1461 + cache: # Cache configuration + ehcache: # Ehcache configuration + time-to-live-seconds: 3600 # By default objects stay 1 hour in the cache + max-entries: 1000 # Number of objects in each cache entry + security: + authentication: + jwt: + # This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one) + # As this is the PRODUCTION configuration, you MUST change the default key, and store it securely: + # - In the Consul configserver + # - In a separate `application-prod.yml` file, in the same folder as your executable JAR file + # - In the `JHIPSTER_SECURITY_AUTHENTICATION_JWT_BASE64_SECRET` environment variable + base64-secret: MmQ3MzkwYzgzMzMxOTYwN2IxMGViYTM4MjlmNThkN2JhMzNkMDQ5ZjIyNzJkNDhlNWNkZGNhZDdjYzRiNzIyMjVlYTAwMmI5NTA3ZmIxZTQzODNhZWY5OWFjZTBiNTVmODlhMzY4NDhjMmQ1Y2UzNGMwMTEzNWFiNWU5YWEwMTY= + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 2592000 + mail: # specific JHipster mail property, for standard properties see MailProperties + base-url: http://my-server-url-to-change # Modify according to your server's URL + logging: + use-json-format: false # By default, logs are not in Json format + logstash: # Forward logs to logstash over a socket, used by LoggingConfiguration + enabled: false + host: localhost + port: 5000 + ring-buffer-size: 512 +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-tls.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-tls.yml new file mode 100644 index 0000000..039f6f4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application-tls.yml @@ -0,0 +1,19 @@ +# =================================================================== +# Activate this profile to enable TLS and HTTP/2. +# +# JHipster has generated a self-signed certificate, which will be used to encrypt traffic. +# As your browser will not understand this certificate, you will need to import it. +# +# Another (easiest) solution with Chrome is to enable the "allow-insecure-localhost" flag +# at chrome://flags/#allow-insecure-localhost +# =================================================================== +server: + ssl: + key-store: classpath:config/tls/keystore.p12 + key-store-password: password + key-store-type: PKCS12 + key-alias: selfsigned + ciphers: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA + enabled-protocols: TLSv1.2 + http2: + enabled: true diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application.yml new file mode 100644 index 0000000..3ca2398 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/application.yml @@ -0,0 +1,212 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration will be overridden by the Spring profile you use, +# for example application-dev.yml if you use the "dev" profile. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +--- +# Conditionally disable springdoc on missing api-docs profile +spring: + config: + activate: + on-profile: '!api-docs' +springdoc: + api-docs: + enabled: false +--- +management: + endpoints: + web: + base-path: /management + exposure: + include: + - configprops + - env + - health + - info + - jhimetrics + - jhiopenapigroups + - logfile + - loggers + - prometheus + - threaddump + - caches + - liquibase + endpoint: + health: + show-details: when_authorized + roles: 'ROLE_ADMIN' + probes: + enabled: true + group: + liveness: + include: livenessState + readiness: + include: readinessState,db + jhimetrics: + enabled: true + info: + git: + mode: full + env: + enabled: true + health: + mail: + enabled: false # When using the MailService, configure an SMTP server and set this to true + prometheus: + metrics: + export: + enabled: true + step: 60 + enable: + http: true + jvm: true + logback: true + process: true + system: true + distribution: + percentiles-histogram: + all: true + percentiles: + all: 0, 0.5, 0.75, 0.95, 0.99, 1.0 + tags: + application: ${spring.application.name} + web: + server: + request: + autotime: + enabled: true + +spring: + application: + name: app + profiles: + # The commented value for `active` can be replaced with valid Spring profiles to load. + # Otherwise, it will be filled in by maven when building the JAR file + # Either way, it can be overridden by `--spring.profiles.active` value passed in the commandline or `-Dspring.profiles.active` set in `JAVA_OPTS` + active: #spring.profiles.active# + group: + dev: + - dev + - api-docs + # Uncomment to activate TLS for the dev profile + #- tls + jmx: + enabled: false + data: + jpa: + repositories: + bootstrap-mode: deferred + jpa: + open-in-view: false + properties: + hibernate.jdbc.time_zone: UTC + hibernate.timezone.default_storage: NORMALIZE + hibernate.type.preferred_instant_jdbc_type: TIMESTAMP + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: true + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + # modify batch size as necessary + hibernate.jdbc.batch_size: 25 + hibernate.order_inserts: true + hibernate.order_updates: true + hibernate.query.fail_on_pagination_over_collection_fetch: true + hibernate.query.in_clause_parameter_padding: true + hibernate: + ddl-auto: none + naming: + physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + messages: + basename: i18n/messages + main: + allow-bean-definition-overriding: true + mvc: + problemdetails: + enabled: true + task: + execution: + thread-name-prefix: app-task- + pool: + core-size: 2 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: app-scheduling- + pool: + size: 2 + thymeleaf: + mode: HTML + output: + ansi: + console-available: true + +server: + servlet: + session: + cookie: + http-only: true + +springdoc: + show-actuator: true + +# Properties to be exposed on the /info management endpoint +info: + # Comma separated list of profiles that will trigger the ribbon to show + display-ribbon-on-profiles: 'dev' + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +jhipster: + clientApp: + name: 'appApp' + # By default CORS is disabled. Uncomment to enable. + # cors: + # allowed-origins: "http://localhost:8100,http://localhost:9000" + # allowed-methods: "*" + # allowed-headers: "*" + # exposed-headers: "Authorization,Link,X-Total-Count,X-${jhipster.clientApp.name}-alert,X-${jhipster.clientApp.name}-error,X-${jhipster.clientApp.name}-params" + # allow-credentials: true + # max-age: 1800 + mail: + from: app@localhost + api-docs: + default-include-pattern: /api/** + management-include-pattern: /management/** + title: App API + description: App API documentation + version: 0.0.1 + terms-of-service-url: + contact-name: + contact-url: + contact-email: + license: unlicensed + license-url: + security: + content-security-policy: "default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:" +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml new file mode 100644 index 0000000..e655bd8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/changelog/00000000000000_initial_schema.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/authority.csv b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/authority.csv new file mode 100644 index 0000000..af5c6df --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/authority.csv @@ -0,0 +1,3 @@ +name +ROLE_ADMIN +ROLE_USER diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user.csv b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user.csv new file mode 100644 index 0000000..fbc52da --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user.csv @@ -0,0 +1,3 @@ +id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by +1;admin;$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC;Administrator;Administrator;admin@localhost;;true;en;system;system +2;user;$2a$10$VEjxo0jq2YG9Rbk2HmX9S.k1uZBGYUHdUcid3g/vfiEl7lwWgOH/K;User;User;user@localhost;;true;en;system;system diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user_authority.csv b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user_authority.csv new file mode 100644 index 0000000..01dbdef --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/data/user_authority.csv @@ -0,0 +1,4 @@ +user_id;authority_name +1;ROLE_ADMIN +1;ROLE_USER +2;ROLE_USER diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/master.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/master.xml new file mode 100644 index 0000000..1d197b3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/liquibase/master.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/tls/keystore.p12 b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/tls/keystore.p12 new file mode 100644 index 0000000..c043a1a Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/config/tls/keystore.p12 differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..3e3e74f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Your request cannot be processed +error.subtitle=Sorry, an error has occurred. +error.status=Status: +error.message=Message: + +# Activation email +email.activation.title=app account activation is required +email.activation.greeting=Dear {0} +email.activation.text1=Your app account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=app Team. + +# Creation email +email.creation.text1=Your app account has been created, please click on the URL below to access it: + +# Reset email +email.reset.title=app password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your app account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_de.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_de.properties new file mode 100644 index 0000000..8b747ba --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_de.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Ihre Anfrage kann nicht bearbeitet werden +error.subtitle=Verzeihung, ein Fehler ist aufgetreten. +error.status=Status: +error.message=Meldung: + +# Activation email +email.activation.title=app Aktivierung +email.activation.greeting=Liebe(r) {0} +email.activation.text1=Ihr app Zugang wurde angelegt, bitte klicken Sie unten auf den Link, um diesen zu aktivieren: +email.activation.text2=Grüße, +email.signature=app. + +# Creation email +email.creation.text1=Ihr app Zugang wurde angelegt, bitte klicken Sie auf den Link um sich anzumelden: + +# Reset email +email.reset.title=app Passwort zurücksetzen +email.reset.greeting=Liebe(r) {0} +email.reset.text1=Für Ihren app Account wurde ein neues Passwort angefordert, bitte klicken Sie unten auf den Link, um das Passwort zurückzusetzen: +email.reset.text2=Grüße, diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_en.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_en.properties new file mode 100644 index 0000000..991bcfd --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Your request cannot be processed +error.subtitle=Sorry, an error has occurred. +error.status=Status: +error.message=Message: + +# Activation email +email.activation.title=app account activation +email.activation.greeting=Dear {0} +email.activation.text1=Your app account has been created, please click on the URL below to activate it: +email.activation.text2=Regards, +email.signature=app Team. + +# Creation email +email.creation.text1=Your app account has been created, please click on the URL below to access it: + +# Reset email +email.reset.title=app password reset +email.reset.greeting=Dear {0} +email.reset.text1=For your app account a password reset was requested, please click on the URL below to reset it: +email.reset.text2=Regards, diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_fr.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_fr.properties new file mode 100644 index 0000000..bb32bb5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/i18n/messages_fr.properties @@ -0,0 +1,21 @@ +# Error page +error.title=Votre demande ne peut être traitée +error.subtitle=Désolé, une erreur s'est produite. +error.status=Statut : +error.message=Message : + +# Activation email +email.activation.title=Activation de votre compte app +email.activation.greeting=Cher {0} +email.activation.text1=Votre compte app a été créé, pour l'activer merci de cliquer sur le lien ci-dessous : +email.activation.text2=Cordialement, +email.signature=app. + +# Creation email +email.creation.text1=Votre compte app a été créé, merci de cliquer sur le lien ci-dessous pour y accéder : + +# Reset email +email.reset.title=app Réinitialisation de mot de passe +email.reset.greeting=Cher {0} +email.reset.text1=Un nouveau mot de passe pour votre compte app a été demandé, veuillez cliquer sur le lien ci-dessous pour le réinitialiser : +email.reset.text2=Cordialement, diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/logback-spring.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..ad3dfe3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/logback-spring.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/error.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/error.html new file mode 100644 index 0000000..31ee9d7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/error.html @@ -0,0 +1,94 @@ + + + + + + Your request cannot be processed + + + +
+

Your request cannot be processed :(

+ +

Sorry, an error has occurred.

+ + Status:  ()
+ + Message: 
+
+
+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/activationEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/activationEmail.html new file mode 100644 index 0000000..6be1e4f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/activationEmail.html @@ -0,0 +1,20 @@ + + + + JHipster activation + + + + +

Dear

+

Your JHipster account has been created, please click on the URL below to activate it:

+

+ Activation link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/creationEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/creationEmail.html new file mode 100644 index 0000000..ab46621 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/creationEmail.html @@ -0,0 +1,20 @@ + + + + JHipster creation + + + + +

Dear

+

Your JHipster account has been created, please click on the URL below to access it:

+

+ Login link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/passwordResetEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/passwordResetEmail.html new file mode 100644 index 0000000..eb74196 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/resources/templates/mail/passwordResetEmail.html @@ -0,0 +1,22 @@ + + + + JHipster password reset + + + + +

Dear

+

+ For your JHipster account a password reset was requested, please click on the URL below to reset it: +

+

+ Login link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/404.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/404.html new file mode 100644 index 0000000..3202d9a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/404.html @@ -0,0 +1,58 @@ + + + + + Page Not Found + + + + + +

Page Not Found

+

Sorry, but the page you were trying to view does not exist.

+ + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/WEB-INF/web.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..f1611b5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,13 @@ + + + + + html + text/html;charset=utf-8 + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/account.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/account.route.ts new file mode 100644 index 0000000..a1a8b46 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/account.route.ts @@ -0,0 +1,19 @@ +import { Routes } from '@angular/router'; + +import activateRoute from './activate/activate.route'; +import passwordRoute from './password/password.route'; +import passwordResetFinishRoute from './password-reset/finish/password-reset-finish.route'; +import passwordResetInitRoute from './password-reset/init/password-reset-init.route'; +import registerRoute from './register/register.route'; +import settingsRoute from './settings/settings.route'; + +const accountRoutes: Routes = [ + activateRoute, + passwordRoute, + passwordResetFinishRoute, + passwordResetInitRoute, + registerRoute, + settingsRoute, +]; + +export default accountRoutes; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.html new file mode 100644 index 0000000..4cd5837 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.html @@ -0,0 +1,16 @@ +
+
+
+

Activation

+ +
+ Your user account has been activated. Please + sign in. +
+ +
+ Your user could not be activated. Please use the registration form to sign up. +
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.spec.ts new file mode 100644 index 0000000..2a22767 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.spec.ts @@ -0,0 +1,68 @@ +import { TestBed, waitForAsync, tick, fakeAsync, inject } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ActivatedRoute } from '@angular/router'; +import { of, throwError } from 'rxjs'; + +import { ActivateService } from './activate.service'; +import ActivateComponent from './activate.component'; + +describe('ActivateComponent', () => { + let comp: ActivateComponent; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, ActivateComponent], + providers: [ + { + provide: ActivatedRoute, + useValue: { queryParams: of({ key: 'ABC123' }) }, + }, + ], + }) + .overrideTemplate(ActivateComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + const fixture = TestBed.createComponent(ActivateComponent); + comp = fixture.componentInstance; + }); + + it('calls activate.get with the key from params', inject( + [ActivateService], + fakeAsync((service: ActivateService) => { + jest.spyOn(service, 'get').mockReturnValue(of()); + + comp.ngOnInit(); + tick(); + + expect(service.get).toHaveBeenCalledWith('ABC123'); + }), + )); + + it('should set set success to true upon successful activation', inject( + [ActivateService], + fakeAsync((service: ActivateService) => { + jest.spyOn(service, 'get').mockReturnValue(of({})); + + comp.ngOnInit(); + tick(); + + expect(comp.error).toBe(false); + expect(comp.success).toBe(true); + }), + )); + + it('should set set error to true upon activation failure', inject( + [ActivateService], + fakeAsync((service: ActivateService) => { + jest.spyOn(service, 'get').mockReturnValue(throwError('ERROR')); + + comp.ngOnInit(); + tick(); + + expect(comp.error).toBe(true); + expect(comp.success).toBe(false); + }), + )); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.ts new file mode 100644 index 0000000..061c703 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.component.ts @@ -0,0 +1,29 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { mergeMap } from 'rxjs/operators'; + +import SharedModule from 'app/shared/shared.module'; +import { ActivateService } from './activate.service'; + +@Component({ + selector: 'jhi-activate', + standalone: true, + imports: [SharedModule, RouterModule], + templateUrl: './activate.component.html', +}) +export default class ActivateComponent implements OnInit { + error = false; + success = false; + + constructor( + private activateService: ActivateService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.queryParams.pipe(mergeMap(params => this.activateService.get(params.key))).subscribe({ + next: () => (this.success = true), + error: () => (this.error = true), + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.route.ts new file mode 100644 index 0000000..5f60255 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.route.ts @@ -0,0 +1,11 @@ +import { Route } from '@angular/router'; + +import ActivateComponent from './activate.component'; + +const activateRoute: Route = { + path: 'activate', + component: ActivateComponent, + title: 'activate.title', +}; + +export default activateRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.spec.ts new file mode 100644 index 0000000..e33b3bf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.spec.ts @@ -0,0 +1,47 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { ActivateService } from './activate.service'; + +describe('ActivateService Service', () => { + let service: ActivateService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(ActivateService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call api/activate endpoint with correct values', () => { + // GIVEN + let expectedResult; + const key = 'key'; + const value = true; + + // WHEN + service.get(key).subscribe(received => { + expectedResult = received; + }); + const testRequest = httpMock.expectOne({ + method: 'GET', + url: applicationConfigService.getEndpointFor(`api/activate?key=${key}`), + }); + testRequest.flush(value); + + // THEN + expect(expectedResult).toEqual(value); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.ts new file mode 100644 index 0000000..9e19475 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/activate/activate.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; + +@Injectable({ providedIn: 'root' }) +export class ActivateService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + get(key: string): Observable<{}> { + return this.http.get(this.applicationConfigService.getEndpointFor('api/activate'), { + params: new HttpParams().set('key', key), + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html new file mode 100644 index 0000000..4967c64 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.html @@ -0,0 +1,131 @@ +
+
+
+

Reset password

+ +
+ The reset key is missing. +
+ +
+ Choose a new password +
+ +
+ Your password couldn't be reset. Remember a password request is only valid for 24 hours. +
+ +
+ Your password has been reset. Please + sign in. +
+ +
+ The password and its confirmation do not match! +
+ +
+
+
+ + + +
+ Your password is required. + + Your password is required to be at least 4 characters. + + Your password cannot be longer than 50 characters. +
+ + +
+ +
+ + + +
+ Your confirmation password is required. + + Your confirmation password is required to be at least 4 characters. + + Your confirmation password cannot be longer than 50 characters. +
+
+ + +
+
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.spec.ts new file mode 100644 index 0000000..2ed7ba2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.spec.ts @@ -0,0 +1,97 @@ +import { ElementRef } from '@angular/core'; +import { ComponentFixture, TestBed, inject, tick, fakeAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { of, throwError } from 'rxjs'; + +import PasswordResetFinishComponent from './password-reset-finish.component'; +import { PasswordResetFinishService } from './password-reset-finish.service'; + +describe('PasswordResetFinishComponent', () => { + let fixture: ComponentFixture; + let comp: PasswordResetFinishComponent; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, PasswordResetFinishComponent], + providers: [ + FormBuilder, + { + provide: ActivatedRoute, + useValue: { queryParams: of({ key: 'XYZPDQ' }) }, + }, + ], + }) + .overrideTemplate(PasswordResetFinishComponent, '') + .createComponent(PasswordResetFinishComponent); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordResetFinishComponent); + comp = fixture.componentInstance; + comp.ngOnInit(); + }); + + it('should define its initial state', () => { + expect(comp.initialized).toBe(true); + expect(comp.key).toEqual('XYZPDQ'); + }); + + it('sets focus after the view has been initialized', () => { + const node = { + focus: jest.fn(), + }; + comp.newPassword = new ElementRef(node); + + comp.ngAfterViewInit(); + + expect(node.focus).toHaveBeenCalled(); + }); + + it('should ensure the two passwords entered match', () => { + comp.passwordForm.patchValue({ + newPassword: 'password', + confirmPassword: 'non-matching', + }); + + comp.finishReset(); + + expect(comp.doNotMatch).toBe(true); + }); + + it('should update success to true after resetting password', inject( + [PasswordResetFinishService], + fakeAsync((service: PasswordResetFinishService) => { + jest.spyOn(service, 'save').mockReturnValue(of({})); + comp.passwordForm.patchValue({ + newPassword: 'password', + confirmPassword: 'password', + }); + + comp.finishReset(); + tick(); + + expect(service.save).toHaveBeenCalledWith('XYZPDQ', 'password'); + expect(comp.success).toBe(true); + }), + )); + + it('should notify of generic error', inject( + [PasswordResetFinishService], + fakeAsync((service: PasswordResetFinishService) => { + jest.spyOn(service, 'save').mockReturnValue(throwError('ERROR')); + comp.passwordForm.patchValue({ + newPassword: 'password', + confirmPassword: 'password', + }); + + comp.finishReset(); + tick(); + + expect(service.save).toHaveBeenCalledWith('XYZPDQ', 'password'); + expect(comp.success).toBe(false); + expect(comp.error).toBe(true); + }), + )); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts new file mode 100644 index 0000000..00d5758 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit, AfterViewInit, ElementRef, ViewChild } from '@angular/core'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import PasswordStrengthBarComponent from 'app/account/password/password-strength-bar/password-strength-bar.component'; +import SharedModule from 'app/shared/shared.module'; + +import { PasswordResetFinishService } from './password-reset-finish.service'; + +@Component({ + selector: 'jhi-password-reset-finish', + standalone: true, + imports: [SharedModule, RouterModule, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent], + templateUrl: './password-reset-finish.component.html', +}) +export default class PasswordResetFinishComponent implements OnInit, AfterViewInit { + @ViewChild('newPassword', { static: false }) + newPassword?: ElementRef; + + initialized = false; + doNotMatch = false; + error = false; + success = false; + key = ''; + + passwordForm = new FormGroup({ + newPassword: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + confirmPassword: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + }); + + constructor( + private passwordResetFinishService: PasswordResetFinishService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + if (params['key']) { + this.key = params['key']; + } + this.initialized = true; + }); + } + + ngAfterViewInit(): void { + if (this.newPassword) { + this.newPassword.nativeElement.focus(); + } + } + + finishReset(): void { + this.doNotMatch = false; + this.error = false; + + const { newPassword, confirmPassword } = this.passwordForm.getRawValue(); + + if (newPassword !== confirmPassword) { + this.doNotMatch = true; + } else { + this.passwordResetFinishService.save(this.key, newPassword).subscribe({ + next: () => (this.success = true), + error: () => (this.error = true), + }); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts new file mode 100644 index 0000000..2963d15 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.route.ts @@ -0,0 +1,11 @@ +import { Route } from '@angular/router'; + +import PasswordResetFinishComponent from './password-reset-finish.component'; + +const passwordResetFinishRoute: Route = { + path: 'reset/finish', + component: PasswordResetFinishComponent, + title: 'global.menu.account.password', +}; + +export default passwordResetFinishRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.spec.ts new file mode 100644 index 0000000..da6a825 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.spec.ts @@ -0,0 +1,44 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { PasswordResetFinishService } from './password-reset-finish.service'; + +describe('PasswordResetFinish Service', () => { + let service: PasswordResetFinishService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(PasswordResetFinishService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call reset-password/finish endpoint with correct values', () => { + // GIVEN + const key = 'abc'; + const newPassword = 'password'; + + // WHEN + service.save(key, newPassword).subscribe(); + + const testRequest = httpMock.expectOne({ + method: 'POST', + url: applicationConfigService.getEndpointFor('api/account/reset-password/finish'), + }); + + // THEN + expect(testRequest.request.body).toEqual({ key, newPassword }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts new file mode 100644 index 0000000..3696e10 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/finish/password-reset-finish.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; + +@Injectable({ providedIn: 'root' }) +export class PasswordResetFinishService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + save(key: string, newPassword: string): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor('api/account/reset-password/finish'), { key, newPassword }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html new file mode 100644 index 0000000..9af8809 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.html @@ -0,0 +1,77 @@ +
+
+
+

Reset your password

+ + + +
+ Enter the email address you used to register +
+ +
+ Check your email for details on how to reset your password. +
+ +
+
+ + + +
+ Your email is required. + + Your email is invalid. + + Your email is required to be at least 5 characters. + + Your email cannot be longer than 50 characters. +
+
+ + +
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.spec.ts new file mode 100644 index 0000000..42f8965 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.spec.ts @@ -0,0 +1,62 @@ +import { ElementRef } from '@angular/core'; +import { ComponentFixture, TestBed, inject } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { of, throwError } from 'rxjs'; + +import PasswordResetInitComponent from './password-reset-init.component'; +import { PasswordResetInitService } from './password-reset-init.service'; + +describe('PasswordResetInitComponent', () => { + let fixture: ComponentFixture; + let comp: PasswordResetInitComponent; + + beforeEach(() => { + fixture = TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, PasswordResetInitComponent], + providers: [FormBuilder], + }) + .overrideTemplate(PasswordResetInitComponent, '') + .createComponent(PasswordResetInitComponent); + comp = fixture.componentInstance; + }); + + it('sets focus after the view has been initialized', () => { + const node = { + focus: jest.fn(), + }; + comp.email = new ElementRef(node); + + comp.ngAfterViewInit(); + + expect(node.focus).toHaveBeenCalled(); + }); + + it('notifies of success upon successful requestReset', inject([PasswordResetInitService], (service: PasswordResetInitService) => { + jest.spyOn(service, 'save').mockReturnValue(of({})); + comp.resetRequestForm.patchValue({ + email: 'user@domain.com', + }); + + comp.requestReset(); + + expect(service.save).toHaveBeenCalledWith('user@domain.com'); + expect(comp.success).toBe(true); + })); + + it('no notification of success upon error response', inject([PasswordResetInitService], (service: PasswordResetInitService) => { + jest.spyOn(service, 'save').mockReturnValue( + throwError({ + status: 503, + data: 'something else', + }), + ); + comp.resetRequestForm.patchValue({ + email: 'user@domain.com', + }); + comp.requestReset(); + + expect(service.save).toHaveBeenCalledWith('user@domain.com'); + expect(comp.success).toBe(false); + })); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts new file mode 100644 index 0000000..71cc741 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.component.ts @@ -0,0 +1,36 @@ +import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core'; +import { FormBuilder, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import SharedModule from 'app/shared/shared.module'; + +import { PasswordResetInitService } from './password-reset-init.service'; + +@Component({ + selector: 'jhi-password-reset-init', + standalone: true, + imports: [SharedModule, FormsModule, ReactiveFormsModule], + templateUrl: './password-reset-init.component.html', +}) +export default class PasswordResetInitComponent implements AfterViewInit { + @ViewChild('email', { static: false }) + email?: ElementRef; + + success = false; + resetRequestForm = this.fb.group({ + email: ['', [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email]], + }); + + constructor( + private passwordResetInitService: PasswordResetInitService, + private fb: FormBuilder, + ) {} + + ngAfterViewInit(): void { + if (this.email) { + this.email.nativeElement.focus(); + } + } + + requestReset(): void { + this.passwordResetInitService.save(this.resetRequestForm.get(['email'])!.value).subscribe(() => (this.success = true)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts new file mode 100644 index 0000000..654634b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.route.ts @@ -0,0 +1,11 @@ +import { Route } from '@angular/router'; + +import PasswordResetInitComponent from './password-reset-init.component'; + +const passwordResetInitRoute: Route = { + path: 'reset/request', + component: PasswordResetInitComponent, + title: 'global.menu.account.password', +}; + +export default passwordResetInitRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.spec.ts new file mode 100644 index 0000000..3a05b2d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.spec.ts @@ -0,0 +1,43 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { PasswordResetInitService } from './password-reset-init.service'; + +describe('PasswordResetInit Service', () => { + let service: PasswordResetInitService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(PasswordResetInitService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call reset-password/init endpoint with correct values', () => { + // GIVEN + const mail = 'test@test.com'; + + // WHEN + service.save(mail).subscribe(); + + const testRequest = httpMock.expectOne({ + method: 'POST', + url: applicationConfigService.getEndpointFor('api/account/reset-password/init'), + }); + + // THEN + expect(testRequest.request.body).toEqual(mail); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts new file mode 100644 index 0000000..31ed7e8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password-reset/init/password-reset-init.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; + +@Injectable({ providedIn: 'root' }) +export class PasswordResetInitService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + save(mail: string): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor('api/account/reset-password/init'), mail); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.html new file mode 100644 index 0000000..244da08 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.html @@ -0,0 +1,10 @@ +
+ Password strength: +
    +
  • +
  • +
  • +
  • +
  • +
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.scss new file mode 100644 index 0000000..67ce468 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.scss @@ -0,0 +1,23 @@ +/* ========================================================================== +start Password strength bar style +========================================================================== */ +ul#strength { + display: inline; + list-style: none; + margin: 0; + margin-left: 15px; + padding: 0; + vertical-align: 2px; +} + +.point { + background: #ddd; + border-radius: 2px; + display: inline-block; + height: 5px; + margin-right: 1px; + width: 20px; + &:last-child { + margin: 0 !important; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.spec.ts new file mode 100644 index 0000000..9fd533a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.spec.ts @@ -0,0 +1,46 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import PasswordStrengthBarComponent from './password-strength-bar.component'; + +describe('PasswordStrengthBarComponent', () => { + let comp: PasswordStrengthBarComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [PasswordStrengthBarComponent], + }) + .overrideTemplate(PasswordStrengthBarComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordStrengthBarComponent); + comp = fixture.componentInstance; + }); + + describe('PasswordStrengthBarComponents', () => { + it('should initialize with default values', () => { + expect(comp.measureStrength('')).toBe(0); + expect(comp.colors).toEqual(['#F00', '#F90', '#FF0', '#9F0', '#0F0']); + expect(comp.getColor(0).idx).toBe(1); + expect(comp.getColor(0).color).toBe(comp.colors[0]); + }); + + it('should increase strength upon password value change', () => { + expect(comp.measureStrength('')).toBe(0); + expect(comp.measureStrength('aa')).toBeGreaterThanOrEqual(comp.measureStrength('')); + expect(comp.measureStrength('aa^6')).toBeGreaterThanOrEqual(comp.measureStrength('aa')); + expect(comp.measureStrength('Aa090(**)')).toBeGreaterThanOrEqual(comp.measureStrength('aa^6')); + expect(comp.measureStrength('Aa090(**)+-07365')).toBeGreaterThanOrEqual(comp.measureStrength('Aa090(**)')); + }); + + it('should change the color based on strength', () => { + expect(comp.getColor(0).color).toBe(comp.colors[0]); + expect(comp.getColor(11).color).toBe(comp.colors[1]); + expect(comp.getColor(22).color).toBe(comp.colors[2]); + expect(comp.getColor(33).color).toBe(comp.colors[3]); + expect(comp.getColor(44).color).toBe(comp.colors[4]); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.ts new file mode 100644 index 0000000..7f703fa --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password-strength-bar/password-strength-bar.component.ts @@ -0,0 +1,79 @@ +import { Component, ElementRef, Input, Renderer2 } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; + +@Component({ + selector: 'jhi-password-strength-bar', + standalone: true, + imports: [SharedModule], + templateUrl: './password-strength-bar.component.html', + styleUrls: ['./password-strength-bar.component.scss'], +}) +export default class PasswordStrengthBarComponent { + colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0']; + + constructor( + private renderer: Renderer2, + private elementRef: ElementRef, + ) {} + + measureStrength(p: string): number { + let force = 0; + const regex = /[$-/:-?{-~!"^_`[\]]/g; // " + const lowerLetters = /[a-z]+/.test(p); + const upperLetters = /[A-Z]+/.test(p); + const numbers = /\d+/.test(p); + const symbols = regex.test(p); + + const flags = [lowerLetters, upperLetters, numbers, symbols]; + const passedMatches = flags.filter((isMatchedFlag: boolean) => isMatchedFlag === true).length; + + force += 2 * p.length + (p.length >= 10 ? 1 : 0); + force += passedMatches * 10; + + // penalty (short password) + force = p.length <= 6 ? Math.min(force, 10) : force; + + // penalty (poor variety of characters) + force = passedMatches === 1 ? Math.min(force, 10) : force; + force = passedMatches === 2 ? Math.min(force, 20) : force; + force = passedMatches === 3 ? Math.min(force, 40) : force; + + return force; + } + + getColor(s: number): { idx: number; color: string } { + let idx = 0; + if (s > 10) { + if (s <= 20) { + idx = 1; + } else if (s <= 30) { + idx = 2; + } else if (s <= 40) { + idx = 3; + } else { + idx = 4; + } + } + return { idx: idx + 1, color: this.colors[idx] }; + } + + @Input() + set passwordToCheck(password: string) { + if (password) { + const c = this.getColor(this.measureStrength(password)); + const element = this.elementRef.nativeElement; + if (element.className) { + this.renderer.removeClass(element, element.className); + } + const lis = element.getElementsByTagName('li'); + for (let i = 0; i < lis.length; i++) { + if (i < c.idx) { + this.renderer.setStyle(lis[i], 'backgroundColor', c.color); + } else { + this.renderer.setStyle(lis[i], 'backgroundColor', '#DDD'); + } + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.html new file mode 100644 index 0000000..496484b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.html @@ -0,0 +1,143 @@ +
+
+
+

+ Password for [{{ account.login }}] +

+ +
Password changed!
+ +
+ An error has occurred! The password could not be changed. +
+ +
+ The password and its confirmation do not match! +
+ +
+
+ + + +
+ Your password is required. +
+
+ +
+ + + +
+ Your password is required. + + Your password is required to be at least 4 characters. + + Your password cannot be longer than 50 characters. +
+ + +
+ +
+ + + +
+ Your confirmation password is required. + + Your confirmation password is required to be at least 4 characters. + + Your confirmation password cannot be longer than 50 characters. +
+
+ + +
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.spec.ts new file mode 100644 index 0000000..50cd6c0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.spec.ts @@ -0,0 +1,103 @@ +jest.mock('app/core/auth/account.service'); + +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpResponse } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { of, throwError } from 'rxjs'; + +import { AccountService } from 'app/core/auth/account.service'; + +import PasswordComponent from './password.component'; +import { PasswordService } from './password.service'; + +describe('PasswordComponent', () => { + let comp: PasswordComponent; + let fixture: ComponentFixture; + let service: PasswordService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, PasswordComponent], + providers: [FormBuilder, AccountService], + }) + .overrideTemplate(PasswordComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PasswordComponent); + comp = fixture.componentInstance; + service = TestBed.inject(PasswordService); + }); + + it('should show error if passwords do not match', () => { + // GIVEN + comp.passwordForm.patchValue({ + newPassword: 'password1', + confirmPassword: 'password2', + }); + // WHEN + comp.changePassword(); + // THEN + expect(comp.doNotMatch).toBe(true); + expect(comp.error).toBe(false); + expect(comp.success).toBe(false); + }); + + it('should call Auth.changePassword when passwords match', () => { + // GIVEN + const passwordValues = { + currentPassword: 'oldPassword', + newPassword: 'myPassword', + }; + + jest.spyOn(service, 'save').mockReturnValue(of(new HttpResponse({ body: true }))); + + comp.passwordForm.patchValue({ + currentPassword: passwordValues.currentPassword, + newPassword: passwordValues.newPassword, + confirmPassword: passwordValues.newPassword, + }); + + // WHEN + comp.changePassword(); + + // THEN + expect(service.save).toHaveBeenCalledWith(passwordValues.newPassword, passwordValues.currentPassword); + }); + + it('should set success to true upon success', () => { + // GIVEN + jest.spyOn(service, 'save').mockReturnValue(of(new HttpResponse({ body: true }))); + comp.passwordForm.patchValue({ + newPassword: 'myPassword', + confirmPassword: 'myPassword', + }); + + // WHEN + comp.changePassword(); + + // THEN + expect(comp.doNotMatch).toBe(false); + expect(comp.error).toBe(false); + expect(comp.success).toBe(true); + }); + + it('should notify of error if change password fails', () => { + // GIVEN + jest.spyOn(service, 'save').mockReturnValue(throwError('ERROR')); + comp.passwordForm.patchValue({ + newPassword: 'myPassword', + confirmPassword: 'myPassword', + }); + + // WHEN + comp.changePassword(); + + // THEN + expect(comp.doNotMatch).toBe(false); + expect(comp.success).toBe(false); + expect(comp.error).toBe(true); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.ts new file mode 100644 index 0000000..7a08643 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.component.ts @@ -0,0 +1,58 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { Observable } from 'rxjs'; + +import SharedModule from 'app/shared/shared.module'; +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; +import { PasswordService } from './password.service'; +import PasswordStrengthBarComponent from './password-strength-bar/password-strength-bar.component'; + +@Component({ + selector: 'jhi-password', + standalone: true, + imports: [SharedModule, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent], + templateUrl: './password.component.html', +}) +export default class PasswordComponent implements OnInit { + doNotMatch = false; + error = false; + success = false; + account$?: Observable; + passwordForm = new FormGroup({ + currentPassword: new FormControl('', { nonNullable: true, validators: Validators.required }), + newPassword: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + confirmPassword: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + }); + + constructor( + private passwordService: PasswordService, + private accountService: AccountService, + ) {} + + ngOnInit(): void { + this.account$ = this.accountService.identity(); + } + + changePassword(): void { + this.error = false; + this.success = false; + this.doNotMatch = false; + + const { newPassword, confirmPassword, currentPassword } = this.passwordForm.getRawValue(); + if (newPassword !== confirmPassword) { + this.doNotMatch = true; + } else { + this.passwordService.save(newPassword, currentPassword).subscribe({ + next: () => (this.success = true), + error: () => (this.error = true), + }); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.route.ts new file mode 100644 index 0000000..96faba9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.route.ts @@ -0,0 +1,13 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; +import PasswordComponent from './password.component'; + +const passwordRoute: Route = { + path: 'password', + component: PasswordComponent, + title: 'global.menu.account.password', + canActivate: [UserRouteAccessService], +}; + +export default passwordRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.spec.ts new file mode 100644 index 0000000..b669e5a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.spec.ts @@ -0,0 +1,44 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { PasswordService } from './password.service'; + +describe('Password Service', () => { + let service: PasswordService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(PasswordService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call change-password endpoint with correct values', () => { + // GIVEN + const password1 = 'password1'; + const password2 = 'password2'; + + // WHEN + service.save(password2, password1).subscribe(); + + const testRequest = httpMock.expectOne({ + method: 'POST', + url: applicationConfigService.getEndpointFor('api/account/change-password'), + }); + + // THEN + expect(testRequest.request.body).toEqual({ currentPassword: password1, newPassword: password2 }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.ts new file mode 100644 index 0000000..e29258f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/password/password.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; + +@Injectable({ providedIn: 'root' }) +export class PasswordService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + save(newPassword: string, currentPassword: string): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor('api/account/change-password'), { currentPassword, newPassword }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.html new file mode 100644 index 0000000..ec7b42e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.html @@ -0,0 +1,220 @@ +
+
+
+

Registration

+ +
+ Registration saved! Please check your email for confirmation. +
+ +
+ Registration failed! Please try again later. +
+ +
+ Login name already registered! Please choose another one. +
+ +
+ Email is already in use! Please choose another one. +
+ +
+ The password and its confirmation do not match! +
+
+
+ +
+
+
+
+ + + +
+ Your username is required. + + Your username is required to be at least 1 character. + + Your username cannot be longer than 50 characters. + + Your username is invalid. +
+
+ +
+ + + +
+ Your email is required. + + Your email is invalid. + + Your email is required to be at least 5 characters. + + Your email cannot be longer than 50 characters. +
+
+ +
+ + + +
+ Your password is required. + + Your password is required to be at least 4 characters. + + Your password cannot be longer than 50 characters. +
+ + +
+ +
+ + + +
+ Your confirmation password is required. + + Your confirmation password is required to be at least 4 characters. + + Your confirmation password cannot be longer than 50 characters. +
+
+ + +
+ +
+ If you want to + sign in, you can try the default accounts:
- Administrator (login="admin" and password="admin")
- User (login="user" and + password="user").
+
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.spec.ts new file mode 100644 index 0000000..1fe62c1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.spec.ts @@ -0,0 +1,134 @@ +import { ComponentFixture, TestBed, waitForAsync, inject, tick, fakeAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { of, throwError } from 'rxjs'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +import { EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/config/error.constants'; + +import { RegisterService } from './register.service'; +import RegisterComponent from './register.component'; + +describe('RegisterComponent', () => { + let fixture: ComponentFixture; + let comp: RegisterComponent; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), HttpClientTestingModule, RegisterComponent], + providers: [FormBuilder], + }) + .overrideTemplate(RegisterComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RegisterComponent); + comp = fixture.componentInstance; + }); + + it('should ensure the two passwords entered match', () => { + comp.registerForm.patchValue({ + password: 'password', + confirmPassword: 'non-matching', + }); + + comp.register(); + + expect(comp.doNotMatch).toBe(true); + }); + + it('should update success to true after creating an account', inject( + [RegisterService, TranslateService], + fakeAsync((service: RegisterService, mockTranslateService: TranslateService) => { + jest.spyOn(service, 'save').mockReturnValue(of({})); + mockTranslateService.currentLang = 'en'; + comp.registerForm.patchValue({ + password: 'password', + confirmPassword: 'password', + }); + + comp.register(); + tick(); + + expect(service.save).toHaveBeenCalledWith({ + email: '', + password: 'password', + login: '', + langKey: 'en', + }); + expect(comp.success).toBe(true); + expect(comp.errorUserExists).toBe(false); + expect(comp.errorEmailExists).toBe(false); + expect(comp.error).toBe(false); + }), + )); + + it('should notify of user existence upon 400/login already in use', inject( + [RegisterService], + fakeAsync((service: RegisterService) => { + jest.spyOn(service, 'save').mockReturnValue( + throwError({ + status: 400, + error: { type: LOGIN_ALREADY_USED_TYPE }, + }), + ); + comp.registerForm.patchValue({ + password: 'password', + confirmPassword: 'password', + }); + + comp.register(); + tick(); + + expect(comp.errorUserExists).toBe(true); + expect(comp.errorEmailExists).toBe(false); + expect(comp.error).toBe(false); + }), + )); + + it('should notify of email existence upon 400/email address already in use', inject( + [RegisterService], + fakeAsync((service: RegisterService) => { + jest.spyOn(service, 'save').mockReturnValue( + throwError({ + status: 400, + error: { type: EMAIL_ALREADY_USED_TYPE }, + }), + ); + comp.registerForm.patchValue({ + password: 'password', + confirmPassword: 'password', + }); + + comp.register(); + tick(); + + expect(comp.errorEmailExists).toBe(true); + expect(comp.errorUserExists).toBe(false); + expect(comp.error).toBe(false); + }), + )); + + it('should notify of generic error', inject( + [RegisterService], + fakeAsync((service: RegisterService) => { + jest.spyOn(service, 'save').mockReturnValue( + throwError({ + status: 503, + }), + ); + comp.registerForm.patchValue({ + password: 'password', + confirmPassword: 'password', + }); + + comp.register(); + tick(); + + expect(comp.errorUserExists).toBe(false); + expect(comp.errorEmailExists).toBe(false); + expect(comp.error).toBe(true); + }), + )); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.ts new file mode 100644 index 0000000..1210bbc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.component.ts @@ -0,0 +1,89 @@ +import { Component, AfterViewInit, ElementRef, ViewChild } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { RouterModule } from '@angular/router'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; + +import { EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/config/error.constants'; +import SharedModule from 'app/shared/shared.module'; +import PasswordStrengthBarComponent from '../password/password-strength-bar/password-strength-bar.component'; +import { RegisterService } from './register.service'; + +@Component({ + selector: 'jhi-register', + standalone: true, + imports: [SharedModule, RouterModule, FormsModule, ReactiveFormsModule, PasswordStrengthBarComponent], + templateUrl: './register.component.html', +}) +export default class RegisterComponent implements AfterViewInit { + @ViewChild('login', { static: false }) + login?: ElementRef; + + doNotMatch = false; + error = false; + errorEmailExists = false; + errorUserExists = false; + success = false; + + registerForm = new FormGroup({ + login: new FormControl('', { + nonNullable: true, + validators: [ + Validators.required, + Validators.minLength(1), + Validators.maxLength(50), + Validators.pattern('^[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$|^[_.@A-Za-z0-9-]+$'), + ], + }), + email: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email], + }), + password: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + confirmPassword: new FormControl('', { + nonNullable: true, + validators: [Validators.required, Validators.minLength(4), Validators.maxLength(50)], + }), + }); + + constructor( + private translateService: TranslateService, + private registerService: RegisterService, + ) {} + + ngAfterViewInit(): void { + if (this.login) { + this.login.nativeElement.focus(); + } + } + + register(): void { + this.doNotMatch = false; + this.error = false; + this.errorEmailExists = false; + this.errorUserExists = false; + + const { password, confirmPassword } = this.registerForm.getRawValue(); + if (password !== confirmPassword) { + this.doNotMatch = true; + } else { + const { login, email } = this.registerForm.getRawValue(); + this.registerService + .save({ login, email, password, langKey: this.translateService.currentLang }) + .subscribe({ next: () => (this.success = true), error: response => this.processError(response) }); + } + } + + private processError(response: HttpErrorResponse): void { + if (response.status === 400 && response.error.type === LOGIN_ALREADY_USED_TYPE) { + this.errorUserExists = true; + } else if (response.status === 400 && response.error.type === EMAIL_ALREADY_USED_TYPE) { + this.errorEmailExists = true; + } else { + this.error = true; + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.model.ts new file mode 100644 index 0000000..02e04a1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.model.ts @@ -0,0 +1,8 @@ +export class Registration { + constructor( + public login: string, + public email: string, + public password: string, + public langKey: string, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.route.ts new file mode 100644 index 0000000..81323c3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.route.ts @@ -0,0 +1,11 @@ +import { Route } from '@angular/router'; + +import RegisterComponent from './register.component'; + +const registerRoute: Route = { + path: 'register', + component: RegisterComponent, + title: 'register.title', +}; + +export default registerRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.spec.ts new file mode 100644 index 0000000..fb9d8a7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.spec.ts @@ -0,0 +1,48 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { RegisterService } from './register.service'; +import { Registration } from './register.model'; + +describe('RegisterService Service', () => { + let service: RegisterService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(RegisterService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call register endpoint with correct values', () => { + // GIVEN + const login = 'abc'; + const email = 'test@test.com'; + const password = 'pass'; + const langKey = 'FR'; + const registration = new Registration(login, email, password, langKey); + + // WHEN + service.save(registration).subscribe(); + + const testRequest = httpMock.expectOne({ + method: 'POST', + url: applicationConfigService.getEndpointFor('api/register'), + }); + + // THEN + expect(testRequest.request.body).toEqual({ email, langKey, login, password }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.ts new file mode 100644 index 0000000..29c1833 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/register/register.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { Registration } from './register.model'; + +@Injectable({ providedIn: 'root' }) +export class RegisterService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + save(registration: Registration): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor('api/register'), registration); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.html new file mode 100644 index 0000000..9e22867 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.html @@ -0,0 +1,154 @@ +
+
+
+

+ User settings for [{{ settingsForm.value.login }}] +

+ +
Settings saved!
+ + + +
+
+ + + +
+ Your first name is required. + + Your first name is required to be at least 1 character + + Your first name cannot be longer than 50 characters +
+
+ +
+ + + +
+ Your last name is required. + + Your last name is required to be at least 1 character + + Your last name cannot be longer than 50 characters +
+
+ +
+ + + +
+ Your email is required. + + Your email is invalid. + + Your email is required to be at least 5 characters. + + Your email cannot be longer than 50 characters. +
+
+ +
+ + +
+ + +
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.spec.ts new file mode 100644 index 0000000..cad6727 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.spec.ts @@ -0,0 +1,90 @@ +jest.mock('app/core/auth/account.service'); + +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { throwError, of } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; + +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; + +import SettingsComponent from './settings.component'; + +describe('SettingsComponent', () => { + let comp: SettingsComponent; + let fixture: ComponentFixture; + let mockAccountService: AccountService; + const account: Account = { + firstName: 'John', + lastName: 'Doe', + activated: true, + email: 'john.doe@mail.com', + langKey: 'en', + login: 'john', + authorities: [], + imageUrl: '', + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), HttpClientTestingModule, SettingsComponent], + providers: [FormBuilder, AccountService], + }) + .overrideTemplate(SettingsComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SettingsComponent); + comp = fixture.componentInstance; + mockAccountService = TestBed.inject(AccountService); + mockAccountService.identity = jest.fn(() => of(account)); + mockAccountService.getAuthenticationState = jest.fn(() => of(account)); + }); + + it('should send the current identity upon save', () => { + // GIVEN + mockAccountService.save = jest.fn(() => of({})); + const settingsFormValues = { + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@mail.com', + langKey: 'en', + }; + + // WHEN + comp.ngOnInit(); + comp.save(); + + // THEN + expect(mockAccountService.identity).toHaveBeenCalled(); + expect(mockAccountService.save).toHaveBeenCalledWith(account); + expect(mockAccountService.authenticate).toHaveBeenCalledWith(account); + expect(comp.settingsForm.value).toMatchObject(expect.objectContaining(settingsFormValues)); + }); + + it('should notify of success upon successful save', () => { + // GIVEN + mockAccountService.save = jest.fn(() => of({})); + + // WHEN + comp.ngOnInit(); + comp.save(); + + // THEN + expect(comp.success).toBe(true); + }); + + it('should notify of error upon failed save', () => { + // GIVEN + mockAccountService.save = jest.fn(() => throwError('ERROR')); + + // WHEN + comp.ngOnInit(); + comp.save(); + + // THEN + expect(comp.success).toBe(false); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.ts new file mode 100644 index 0000000..1a60d9f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; + +import SharedModule from 'app/shared/shared.module'; +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; +import { LANGUAGES } from 'app/config/language.constants'; + +const initialAccount: Account = {} as Account; + +@Component({ + selector: 'jhi-settings', + standalone: true, + imports: [SharedModule, FormsModule, ReactiveFormsModule], + templateUrl: './settings.component.html', +}) +export default class SettingsComponent implements OnInit { + success = false; + languages = LANGUAGES; + + settingsForm = new FormGroup({ + firstName: new FormControl(initialAccount.firstName, { + nonNullable: true, + validators: [Validators.required, Validators.minLength(1), Validators.maxLength(50)], + }), + lastName: new FormControl(initialAccount.lastName, { + nonNullable: true, + validators: [Validators.required, Validators.minLength(1), Validators.maxLength(50)], + }), + email: new FormControl(initialAccount.email, { + nonNullable: true, + validators: [Validators.required, Validators.minLength(5), Validators.maxLength(254), Validators.email], + }), + langKey: new FormControl(initialAccount.langKey, { nonNullable: true }), + + activated: new FormControl(initialAccount.activated, { nonNullable: true }), + authorities: new FormControl(initialAccount.authorities, { nonNullable: true }), + imageUrl: new FormControl(initialAccount.imageUrl, { nonNullable: true }), + login: new FormControl(initialAccount.login, { nonNullable: true }), + }); + + constructor( + private accountService: AccountService, + private translateService: TranslateService, + ) {} + + ngOnInit(): void { + this.accountService.identity().subscribe(account => { + if (account) { + this.settingsForm.patchValue(account); + } + }); + } + + save(): void { + this.success = false; + + const account = this.settingsForm.getRawValue(); + this.accountService.save(account).subscribe(() => { + this.success = true; + + this.accountService.authenticate(account); + + if (account.langKey !== this.translateService.currentLang) { + this.translateService.use(account.langKey); + } + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.route.ts new file mode 100644 index 0000000..3c93066 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/account/settings/settings.route.ts @@ -0,0 +1,13 @@ +import { Route } from '@angular/router'; + +import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; +import SettingsComponent from './settings.component'; + +const settingsRoute: Route = { + path: 'settings', + component: SettingsComponent, + title: 'global.menu.account.settings', + canActivate: [UserRouteAccessService], +}; + +export default settingsRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/admin-routing.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/admin-routing.module.ts new file mode 100644 index 0000000..34450cf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/admin-routing.module.ts @@ -0,0 +1,43 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; +/* jhipster-needle-add-admin-module-import - JHipster will add admin modules imports here */ + +@NgModule({ + imports: [ + /* jhipster-needle-add-admin-module - JHipster will add admin modules here */ + RouterModule.forChild([ + { + path: 'user-management', + loadChildren: () => import('./user-management/user-management.route'), + title: 'userManagement.home.title', + }, + { + path: 'docs', + loadComponent: () => import('./docs/docs.component'), + title: 'global.menu.admin.apidocs', + }, + { + path: 'configuration', + loadComponent: () => import('./configuration/configuration.component'), + title: 'configuration.title', + }, + { + path: 'health', + loadComponent: () => import('./health/health.component'), + title: 'health.title', + }, + { + path: 'logs', + loadComponent: () => import('./logs/logs.component'), + title: 'logs.title', + }, + { + path: 'metrics', + loadComponent: () => import('./metrics/metrics.component'), + title: 'metrics.title', + }, + /* jhipster-needle-add-admin-route - JHipster will add admin routes here */ + ]), + ], +}) +export default class AdminRoutingModule {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.html new file mode 100644 index 0000000..e76d02c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.html @@ -0,0 +1,57 @@ +
+

Configuration

+ + Filter (by prefix) + + +

Spring configuration

+ + + + + + + + + + + + + + +
+ Prefix + Properties
+ {{ bean.prefix }} + +
+
{{ property.key }}
+
+ {{ property.value | json }} +
+
+
+ +
+

+ {{ propertySource.name }} +

+ + + + + + + + + + + + + + +
PropertyValue
{{ property.key }} + {{ property.value.value }} +
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.spec.ts new file mode 100644 index 0000000..cebc279 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.spec.ts @@ -0,0 +1,66 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + +import ConfigurationComponent from './configuration.component'; +import { ConfigurationService } from './configuration.service'; +import { Bean, PropertySource } from './configuration.model'; + +describe('ConfigurationComponent', () => { + let comp: ConfigurationComponent; + let fixture: ComponentFixture; + let service: ConfigurationService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, ConfigurationComponent], + providers: [ConfigurationService], + }) + .overrideTemplate(ConfigurationComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfigurationComponent); + comp = fixture.componentInstance; + service = TestBed.inject(ConfigurationService); + }); + + describe('OnInit', () => { + it('Should call load all on init', () => { + // GIVEN + const beans: Bean[] = [ + { + prefix: 'jhipster', + properties: { + clientApp: { + name: 'jhipsterApp', + }, + }, + }, + ]; + const propertySources: PropertySource[] = [ + { + name: 'server.ports', + properties: { + 'local.server.port': { + value: '8080', + }, + }, + }, + ]; + jest.spyOn(service, 'getBeans').mockReturnValue(of(beans)); + jest.spyOn(service, 'getPropertySources').mockReturnValue(of(propertySources)); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.getBeans).toHaveBeenCalled(); + expect(service.getPropertySources).toHaveBeenCalled(); + expect(comp.allBeans).toEqual(beans); + expect(comp.beans).toEqual(beans); + expect(comp.propertySources).toEqual(propertySources); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.ts new file mode 100644 index 0000000..ee97f6b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { FormsModule } from '@angular/forms'; +import { SortDirective, SortByDirective } from 'app/shared/sort'; +import { ConfigurationService } from './configuration.service'; +import { Bean, PropertySource } from './configuration.model'; + +@Component({ + standalone: true, + selector: 'jhi-configuration', + templateUrl: './configuration.component.html', + imports: [SharedModule, FormsModule, SortDirective, SortByDirective], +}) +export default class ConfigurationComponent implements OnInit { + allBeans!: Bean[]; + beans: Bean[] = []; + beansFilter = ''; + beansAscending = true; + propertySources: PropertySource[] = []; + + constructor(private configurationService: ConfigurationService) {} + + ngOnInit(): void { + this.configurationService.getBeans().subscribe(beans => { + this.allBeans = beans; + this.filterAndSortBeans(); + }); + + this.configurationService.getPropertySources().subscribe(propertySources => (this.propertySources = propertySources)); + } + + filterAndSortBeans(): void { + const beansAscendingValue = this.beansAscending ? -1 : 1; + const beansAscendingValueReverse = this.beansAscending ? 1 : -1; + this.beans = this.allBeans + .filter(bean => !this.beansFilter || bean.prefix.toLowerCase().includes(this.beansFilter.toLowerCase())) + .sort((a, b) => (a.prefix < b.prefix ? beansAscendingValue : beansAscendingValueReverse)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.model.ts new file mode 100644 index 0000000..6a671e0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.model.ts @@ -0,0 +1,40 @@ +export interface ConfigProps { + contexts: Contexts; +} + +export interface Contexts { + [key: string]: Context; +} + +export interface Context { + beans: Beans; + parentId?: any; +} + +export interface Beans { + [key: string]: Bean; +} + +export interface Bean { + prefix: string; + properties: any; +} + +export interface Env { + activeProfiles?: string[]; + propertySources: PropertySource[]; +} + +export interface PropertySource { + name: string; + properties: Properties; +} + +export interface Properties { + [key: string]: Property; +} + +export interface Property { + value: string; + origin?: string; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.spec.ts new file mode 100644 index 0000000..6e6ff7f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.spec.ts @@ -0,0 +1,71 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ConfigurationService } from './configuration.service'; +import { Bean, ConfigProps, Env, PropertySource } from './configuration.model'; + +describe('Logs Service', () => { + let service: ConfigurationService; + let httpMock: HttpTestingController; + let expectedResult: Bean[] | PropertySource[] | null; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + expectedResult = null; + service = TestBed.inject(ConfigurationService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should get the config', () => { + const bean: Bean = { + prefix: 'jhipster', + properties: { + clientApp: { + name: 'jhipsterApp', + }, + }, + }; + const configProps: ConfigProps = { + contexts: { + jhipster: { + beans: { + 'tech.jhipster.config.JHipsterProperties': bean, + }, + }, + }, + }; + service.getBeans().subscribe(received => (expectedResult = received)); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush(configProps); + expect(expectedResult).toEqual([bean]); + }); + + it('should get the env', () => { + const propertySources: PropertySource[] = [ + { + name: 'server.ports', + properties: { + 'local.server.port': { + value: '8080', + }, + }, + }, + ]; + const env: Env = { propertySources }; + service.getPropertySources().subscribe(received => (expectedResult = received)); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush(env); + expect(expectedResult).toEqual(propertySources); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.ts new file mode 100644 index 0000000..f03a0cc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/configuration/configuration.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { Bean, Beans, ConfigProps, Env, PropertySource } from './configuration.model'; + +@Injectable({ providedIn: 'root' }) +export class ConfigurationService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + getBeans(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/configprops')).pipe( + map(configProps => + Object.values( + Object.values(configProps.contexts) + .map(context => context.beans) + .reduce((allBeans: Beans, contextBeans: Beans) => ({ ...allBeans, ...contextBeans })), + ), + ), + ); + } + + getPropertySources(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/env')).pipe(map(env => env.propertySources)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.html new file mode 100644 index 0000000..2402552 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.html @@ -0,0 +1,10 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.scss new file mode 100644 index 0000000..bb9a6cc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.scss @@ -0,0 +1,6 @@ +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; + +iframe { + background: white; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.ts new file mode 100644 index 0000000..ea41883 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/docs/docs.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'jhi-docs', + templateUrl: './docs.component.html', + styleUrls: ['./docs.component.scss'], +}) +export default class DocsComponent {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.html new file mode 100644 index 0000000..08e62ac --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.html @@ -0,0 +1,48 @@ +
+

+ Health Checks + + +

+ +
+ + + + + + + + + + + + + + + +
Service nameStatusDetails
+ {{ 'health.indicator.' + componentHealth.key | translate }} + + + {{ + { UNKNOWN: 'UNKNOWN', UP: 'UP', OUT_OF_SERVICE: 'OUT_OF_SERVICE', DOWN: 'DOWN' }[componentHealth.value!.status || 'UNKNOWN'] + }} + + + + + +
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.spec.ts new file mode 100644 index 0000000..97ce8ab --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.spec.ts @@ -0,0 +1,65 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of, throwError } from 'rxjs'; + +import HealthComponent from './health.component'; +import { HealthService } from './health.service'; +import { Health } from './health.model'; + +describe('HealthComponent', () => { + let comp: HealthComponent; + let fixture: ComponentFixture; + let service: HealthService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, HealthComponent], + }) + .overrideTemplate(HealthComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthComponent); + comp = fixture.componentInstance; + service = TestBed.inject(HealthService); + }); + + describe('getBadgeClass', () => { + it('should get badge class', () => { + const upBadgeClass = comp.getBadgeClass('UP'); + const downBadgeClass = comp.getBadgeClass('DOWN'); + expect(upBadgeClass).toEqual('bg-success'); + expect(downBadgeClass).toEqual('bg-danger'); + }); + }); + + describe('refresh', () => { + it('should call refresh on init', () => { + // GIVEN + const health: Health = { status: 'UP', components: { mail: { status: 'UP', details: { mailDetail: 'mail' } } } }; + jest.spyOn(service, 'checkHealth').mockReturnValue(of(health)); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.checkHealth).toHaveBeenCalled(); + expect(comp.health).toEqual(health); + }); + + it('should handle a 503 on refreshing health data', () => { + // GIVEN + const health: Health = { status: 'DOWN', components: { mail: { status: 'DOWN' } } }; + jest.spyOn(service, 'checkHealth').mockReturnValue(throwError(new HttpErrorResponse({ status: 503, error: health }))); + + // WHEN + comp.refresh(); + + // THEN + expect(service.checkHealth).toHaveBeenCalled(); + expect(comp.health).toEqual(health); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.ts new file mode 100644 index 0000000..4e33b9c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.component.ts @@ -0,0 +1,50 @@ +import { Component, OnInit } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { HealthService } from './health.service'; +import { Health, HealthDetails, HealthStatus } from './health.model'; +import HealthModalComponent from './modal/health-modal.component'; + +@Component({ + standalone: true, + selector: 'jhi-health', + templateUrl: './health.component.html', + imports: [SharedModule, HealthModalComponent], +}) +export default class HealthComponent implements OnInit { + health?: Health; + + constructor( + private modalService: NgbModal, + private healthService: HealthService, + ) {} + + ngOnInit(): void { + this.refresh(); + } + + getBadgeClass(statusState: HealthStatus): string { + if (statusState === 'UP') { + return 'bg-success'; + } + return 'bg-danger'; + } + + refresh(): void { + this.healthService.checkHealth().subscribe({ + next: health => (this.health = health), + error: (error: HttpErrorResponse) => { + if (error.status === 503) { + this.health = error.error; + } + }, + }); + } + + showHealth(health: { key: string; value: HealthDetails }): void { + const modalRef = this.modalService.open(HealthModalComponent); + modalRef.componentInstance.health = health; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.model.ts new file mode 100644 index 0000000..0811289 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.model.ts @@ -0,0 +1,15 @@ +export type HealthStatus = 'UP' | 'DOWN' | 'UNKNOWN' | 'OUT_OF_SERVICE'; + +export type HealthKey = 'diskSpace' | 'mail' | 'ping' | 'livenessState' | 'readinessState' | 'db'; + +export interface Health { + status: HealthStatus; + components: { + [key in HealthKey]?: HealthDetails; + }; +} + +export interface HealthDetails { + status: HealthStatus; + details?: { [key: string]: unknown }; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.spec.ts new file mode 100644 index 0000000..1e1ff19 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.spec.ts @@ -0,0 +1,48 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { HealthService } from './health.service'; + +describe('HealthService Service', () => { + let service: HealthService; + let httpMock: HttpTestingController; + let applicationConfigService: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(HealthService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should call management/health endpoint with correct values', () => { + // GIVEN + let expectedResult; + const checkHealth = { + components: [], + }; + + // WHEN + service.checkHealth().subscribe(received => { + expectedResult = received; + }); + const testRequest = httpMock.expectOne({ + method: 'GET', + url: applicationConfigService.getEndpointFor('management/health'), + }); + testRequest.flush(checkHealth); + + // THEN + expect(expectedResult).toEqual(checkHealth); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.ts new file mode 100644 index 0000000..7a2d420 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/health.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { Health } from './health.model'; + +@Injectable({ providedIn: 'root' }) +export class HealthService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + checkHealth(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/health')); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.html new file mode 100644 index 0000000..f55b876 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.html @@ -0,0 +1,36 @@ + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.spec.ts new file mode 100644 index 0000000..b9aa047 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.spec.ts @@ -0,0 +1,111 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import HealthModalComponent from './health-modal.component'; + +describe('HealthModalComponent', () => { + let comp: HealthModalComponent; + let fixture: ComponentFixture; + let mockActiveModal: NgbActiveModal; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, HealthModalComponent], + providers: [NgbActiveModal], + }) + .overrideTemplate(HealthModalComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HealthModalComponent); + comp = fixture.componentInstance; + mockActiveModal = TestBed.inject(NgbActiveModal); + }); + + describe('readableValue', () => { + it('should return stringify value', () => { + // GIVEN + comp.health = undefined; + + // WHEN + const result = comp.readableValue({ name: 'jhipster' }); + + // THEN + expect(result).toEqual('{"name":"jhipster"}'); + }); + + it('should return string value', () => { + // GIVEN + comp.health = undefined; + + // WHEN + const result = comp.readableValue('jhipster'); + + // THEN + expect(result).toEqual('jhipster'); + }); + + it('should return storage space in an human readable unit (GB)', () => { + // GIVEN + comp.health = { + key: 'diskSpace', + value: { + status: 'UP', + }, + }; + + // WHEN + const result = comp.readableValue(1073741825); + + // THEN + expect(result).toEqual('1.00 GB'); + }); + + it('should return storage space in an human readable unit (MB)', () => { + // GIVEN + comp.health = { + key: 'diskSpace', + value: { + status: 'UP', + }, + }; + + // WHEN + const result = comp.readableValue(1073741824); + + // THEN + expect(result).toEqual('1024.00 MB'); + }); + + it('should return string value', () => { + // GIVEN + comp.health = { + key: 'mail', + value: { + status: 'UP', + }, + }; + + // WHEN + const result = comp.readableValue(1234); + + // THEN + expect(result).toEqual('1234'); + }); + }); + + describe('dismiss', () => { + it('should call dismiss when dismiss modal is called', () => { + // GIVEN + const spy = jest.spyOn(mockActiveModal, 'dismiss'); + + // WHEN + comp.dismiss(); + + // THEN + expect(spy).toHaveBeenCalled(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.ts new file mode 100644 index 0000000..9eefa8b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/health/modal/health-modal.component.ts @@ -0,0 +1,37 @@ +import { Component } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { HealthKey, HealthDetails } from '../health.model'; + +@Component({ + standalone: true, + selector: 'jhi-health-modal', + templateUrl: './health-modal.component.html', + imports: [SharedModule], +}) +export default class HealthModalComponent { + health?: { key: HealthKey; value: HealthDetails }; + + constructor(private activeModal: NgbActiveModal) {} + + readableValue(value: any): string { + if (this.health?.key === 'diskSpace') { + // Should display storage space in an human readable unit + const val = value / 1073741824; + if (val > 1) { + return `${val.toFixed(2)} GB`; + } + return `${(value / 1048576).toFixed(2)} MB`; + } + + if (typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); + } + + dismiss(): void { + this.activeModal.dismiss(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/log.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/log.model.ts new file mode 100644 index 0000000..2606a88 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/log.model.ts @@ -0,0 +1,18 @@ +export type Level = 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'OFF'; + +export interface Logger { + configuredLevel: Level | null; + effectiveLevel: Level; +} + +export interface LoggersResponse { + levels: Level[]; + loggers: { [key: string]: Logger }; +} + +export class Log { + constructor( + public name: string, + public level: Level, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.html new file mode 100644 index 0000000..256d216 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.html @@ -0,0 +1,78 @@ +
+

Logs

+ +
+
+
+ +

There are {{ loggers.length }} loggers.

+ + Filter + + + + + + + + + + + + + + + + +
Name Level
+ {{ logger.name | slice: 0 : 140 }} + + + + + + + + + + + + +
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.spec.ts new file mode 100644 index 0000000..611b47d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.spec.ts @@ -0,0 +1,82 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + +import LogsComponent from './logs.component'; +import { LogsService } from './logs.service'; +import { Log, LoggersResponse } from './log.model'; + +describe('LogsComponent', () => { + let comp: LogsComponent; + let fixture: ComponentFixture; + let service: LogsService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, LogsComponent], + providers: [LogsService], + }) + .overrideTemplate(LogsComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LogsComponent); + comp = fixture.componentInstance; + service = TestBed.inject(LogsService); + }); + + describe('OnInit', () => { + it('should set all default values correctly', () => { + expect(comp.filter).toBe(''); + expect(comp.orderProp).toBe('name'); + expect(comp.ascending).toBe(true); + }); + + it('Should call load all on init', () => { + // GIVEN + const log = new Log('main', 'WARN'); + jest.spyOn(service, 'findAll').mockReturnValue( + of({ + loggers: { + main: { + effectiveLevel: 'WARN', + }, + }, + } as unknown as LoggersResponse), + ); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.findAll).toHaveBeenCalled(); + expect(comp.loggers?.[0]).toEqual(expect.objectContaining(log)); + }); + }); + + describe('change log level', () => { + it('should change log level correctly', () => { + // GIVEN + const log = new Log('main', 'ERROR'); + jest.spyOn(service, 'changeLevel').mockReturnValue(of({})); + jest.spyOn(service, 'findAll').mockReturnValue( + of({ + loggers: { + main: { + effectiveLevel: 'ERROR', + }, + }, + } as unknown as LoggersResponse), + ); + + // WHEN + comp.changeLevel('main', 'ERROR'); + + // THEN + expect(service.changeLevel).toHaveBeenCalled(); + expect(service.findAll).toHaveBeenCalled(); + expect(comp.loggers?.[0]).toEqual(expect.objectContaining(log)); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.ts new file mode 100644 index 0000000..e899e36 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit } from '@angular/core'; +import { finalize } from 'rxjs/operators'; + +import SharedModule from 'app/shared/shared.module'; +import { FormsModule } from '@angular/forms'; +import { SortDirective, SortByDirective } from 'app/shared/sort'; +import { Log, LoggersResponse, Level } from './log.model'; +import { LogsService } from './logs.service'; + +@Component({ + standalone: true, + selector: 'jhi-logs', + templateUrl: './logs.component.html', + imports: [SharedModule, FormsModule, SortDirective, SortByDirective], +}) +export default class LogsComponent implements OnInit { + loggers?: Log[]; + filteredAndOrderedLoggers?: Log[]; + isLoading = false; + filter = ''; + orderProp: keyof Log = 'name'; + ascending = true; + + constructor(private logsService: LogsService) {} + + ngOnInit(): void { + this.findAndExtractLoggers(); + } + + changeLevel(name: string, level: Level): void { + this.logsService.changeLevel(name, level).subscribe(() => this.findAndExtractLoggers()); + } + + filterAndSort(): void { + this.filteredAndOrderedLoggers = this.loggers!.filter( + logger => !this.filter || logger.name.toLowerCase().includes(this.filter.toLowerCase()), + ).sort((a, b) => { + if (a[this.orderProp] < b[this.orderProp]) { + return this.ascending ? -1 : 1; + } else if (a[this.orderProp] > b[this.orderProp]) { + return this.ascending ? 1 : -1; + } else if (this.orderProp === 'level') { + return a.name < b.name ? -1 : 1; + } + return 0; + }); + } + + private findAndExtractLoggers(): void { + this.isLoading = true; + this.logsService + .findAll() + .pipe( + finalize(() => { + this.filterAndSort(); + this.isLoading = false; + }), + ) + .subscribe({ + next: (response: LoggersResponse) => + (this.loggers = Object.entries(response.loggers).map(([key, logger]) => new Log(key, logger.effectiveLevel))), + error: () => (this.loggers = []), + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.spec.ts new file mode 100644 index 0000000..cebee2c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.spec.ts @@ -0,0 +1,31 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { LogsService } from './logs.service'; + +describe('Logs Service', () => { + let service: LogsService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(LogsService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should change log level', () => { + service.changeLevel('main', 'ERROR').subscribe(); + + const req = httpMock.expectOne({ method: 'POST' }); + expect(req.request.body).toEqual({ configuredLevel: 'ERROR' }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.ts new file mode 100644 index 0000000..61f64f2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/logs/logs.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { LoggersResponse, Level } from './log.model'; + +@Injectable({ providedIn: 'root' }) +export class LogsService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + changeLevel(name: string, configuredLevel: Level): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor(`management/loggers/${name}`), { configuredLevel }); + } + + findAll(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/loggers')); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html new file mode 100644 index 0000000..8e802cc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.html @@ -0,0 +1,28 @@ +

Memory

+ +
+
+ + {{ entry.key }} + ({{ entry.value.used / 1048576 | number: '1.0-0' }}M / {{ entry.value.max / 1048576 | number: '1.0-0' }}M) + + +
Committed : {{ entry.value.committed / 1048576 | number: '1.0-0' }}M
+ + {{ entry.key }} {{ entry.value.used / 1048576 | number: '1.0-0' }}M + + + {{ (entry.value.used * 100) / entry.value.max | number: '1.0-0' }}% + +
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts new file mode 100644 index 0000000..8654a8b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-memory/jvm-memory.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { JvmMetrics } from 'app/admin/metrics/metrics.model'; + +@Component({ + standalone: true, + selector: 'jhi-jvm-memory', + templateUrl: './jvm-memory.component.html', + imports: [SharedModule], +}) +export class JvmMemoryComponent { + /** + * object containing all jvm memory metrics + */ + @Input() jvmMemoryMetrics?: { [key: string]: JvmMetrics }; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.html new file mode 100644 index 0000000..13bbe61 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.html @@ -0,0 +1,55 @@ +

Threads

+ +Runnable {{ threadStats.threadDumpRunnable }} + + + {{ (threadStats.threadDumpRunnable * 100) / threadStats.threadDumpAll | number: '1.0-0' }}% + + +Timed waiting ({{ threadStats.threadDumpTimedWaiting }}) + + + {{ (threadStats.threadDumpTimedWaiting * 100) / threadStats.threadDumpAll | number: '1.0-0' }}% + + +Waiting ({{ threadStats.threadDumpWaiting }}) + + + {{ (threadStats.threadDumpWaiting * 100) / threadStats.threadDumpAll | number: '1.0-0' }}% + + +Blocked ({{ threadStats.threadDumpBlocked }}) + + + {{ (threadStats.threadDumpBlocked * 100) / threadStats.threadDumpAll | number: '1.0-0' }}% + + +
Total: {{ threadStats.threadDumpAll }}
+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.ts new file mode 100644 index 0000000..dc72693 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/jvm-threads/jvm-threads.component.ts @@ -0,0 +1,58 @@ +import { Component, Input } from '@angular/core'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { Thread, ThreadState } from 'app/admin/metrics/metrics.model'; +import { MetricsModalThreadsComponent } from '../metrics-modal-threads/metrics-modal-threads.component'; + +@Component({ + standalone: true, + selector: 'jhi-jvm-threads', + templateUrl: './jvm-threads.component.html', + imports: [SharedModule], +}) +export class JvmThreadsComponent { + threadStats = { + threadDumpAll: 0, + threadDumpRunnable: 0, + threadDumpTimedWaiting: 0, + threadDumpWaiting: 0, + threadDumpBlocked: 0, + }; + + @Input() + set threads(threads: Thread[] | undefined) { + this._threads = threads; + + threads?.forEach(thread => { + if (thread.threadState === ThreadState.Runnable) { + this.threadStats.threadDumpRunnable += 1; + } else if (thread.threadState === ThreadState.Waiting) { + this.threadStats.threadDumpWaiting += 1; + } else if (thread.threadState === ThreadState.TimedWaiting) { + this.threadStats.threadDumpTimedWaiting += 1; + } else if (thread.threadState === ThreadState.Blocked) { + this.threadStats.threadDumpBlocked += 1; + } + }); + + this.threadStats.threadDumpAll = + this.threadStats.threadDumpRunnable + + this.threadStats.threadDumpWaiting + + this.threadStats.threadDumpTimedWaiting + + this.threadStats.threadDumpBlocked; + } + + get threads(): Thread[] | undefined { + return this._threads; + } + + private _threads: Thread[] | undefined; + + constructor(private modalService: NgbModal) {} + + open(): void { + const modalRef = this.modalService.open(MetricsModalThreadsComponent); + modalRef.componentInstance.threads = this.threads; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.html new file mode 100644 index 0000000..18f1bcb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.html @@ -0,0 +1,42 @@ +

Cache statistics

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
Cache nameCache HitsCache MissesCache GetsCache PutsCache RemovalsCache EvictionsCache Hit %Cache Miss %
{{ entry.key }}{{ entry.value['cache.gets.hit'] }}{{ entry.value['cache.gets.miss'] }}{{ entry.value['cache.gets.hit'] + entry.value['cache.gets.miss'] }}{{ entry.value['cache.puts'] }}{{ entry.value['cache.removals'] }}{{ entry.value['cache.evictions'] }} + {{ + filterNaN((100 * entry.value['cache.gets.hit']) / (entry.value['cache.gets.hit'] + entry.value['cache.gets.miss'])) + | number: '1.0-4' + }} + + {{ + filterNaN((100 * entry.value['cache.gets.miss']) / (entry.value['cache.gets.hit'] + entry.value['cache.gets.miss'])) + | number: '1.0-4' + }} +
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts new file mode 100644 index 0000000..5a174f7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-cache/metrics-cache.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { CacheMetrics } from 'app/admin/metrics/metrics.model'; +import { filterNaN } from 'app/core/util/operators'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-cache', + templateUrl: './metrics-cache.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SharedModule], +}) +export class MetricsCacheComponent { + /** + * object containing all cache related metrics + */ + @Input() cacheMetrics?: { [key: string]: CacheMetrics }; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; + + filterNaN = (input: number): number => filterNaN(input); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html new file mode 100644 index 0000000..ab524b8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.html @@ -0,0 +1,57 @@ +

DataSource statistics (time in millisecond)

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Connection Pool Usage (active: {{ datasourceMetrics.active.value }}, min: + {{ datasourceMetrics.min.value }}, max: {{ datasourceMetrics.max.value }}, idle: {{ datasourceMetrics.idle.value }}) + CountMeanMinp50p75p95p99Max
Acquire{{ datasourceMetrics.acquire.count }}{{ filterNaN(datasourceMetrics.acquire.mean) | number: '1.0-2' }}{{ datasourceMetrics.acquire['0.0'] | number: '1.0-3' }}{{ datasourceMetrics.acquire['0.5'] | number: '1.0-3' }}{{ datasourceMetrics.acquire['0.75'] | number: '1.0-3' }}{{ datasourceMetrics.acquire['0.95'] | number: '1.0-3' }}{{ datasourceMetrics.acquire['0.99'] | number: '1.0-3' }}{{ filterNaN(datasourceMetrics.acquire.max) | number: '1.0-2' }}
Creation{{ datasourceMetrics.creation.count }}{{ filterNaN(datasourceMetrics.creation.mean) | number: '1.0-2' }}{{ datasourceMetrics.creation['0.0'] | number: '1.0-3' }}{{ datasourceMetrics.creation['0.5'] | number: '1.0-3' }}{{ datasourceMetrics.creation['0.75'] | number: '1.0-3' }}{{ datasourceMetrics.creation['0.95'] | number: '1.0-3' }}{{ datasourceMetrics.creation['0.99'] | number: '1.0-3' }}{{ filterNaN(datasourceMetrics.creation.max) | number: '1.0-2' }}
Usage{{ datasourceMetrics.usage.count }}{{ filterNaN(datasourceMetrics.usage.mean) | number: '1.0-2' }}{{ datasourceMetrics.usage['0.0'] | number: '1.0-3' }}{{ datasourceMetrics.usage['0.5'] | number: '1.0-3' }}{{ datasourceMetrics.usage['0.75'] | number: '1.0-3' }}{{ datasourceMetrics.usage['0.95'] | number: '1.0-3' }}{{ datasourceMetrics.usage['0.99'] | number: '1.0-3' }}{{ filterNaN(datasourceMetrics.usage.max) | number: '1.0-2' }}
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts new file mode 100644 index 0000000..b666e1a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-datasource/metrics-datasource.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { Databases } from 'app/admin/metrics/metrics.model'; +import { filterNaN } from 'app/core/util/operators'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-datasource', + templateUrl: './metrics-datasource.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SharedModule], +}) +export class MetricsDatasourceComponent { + /** + * object containing all datasource related metrics + */ + @Input() datasourceMetrics?: Databases; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; + + filterNaN = (input: number): number => filterNaN(input); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html new file mode 100644 index 0000000..ee47ec7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.html @@ -0,0 +1,24 @@ +

Endpoints requests (time in millisecond)

+ +
+ + + + + + + + + + + + + + + + + + + +
MethodEndpoint urlCountMean
{{ method.key }}{{ entry.key }}{{ method.value!.count }}{{ method.value!.mean | number: '1.0-3' }}
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts new file mode 100644 index 0000000..f96e80a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-endpoints-requests/metrics-endpoints-requests.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { Services } from 'app/admin/metrics/metrics.model'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-endpoints-requests', + templateUrl: './metrics-endpoints-requests.component.html', + imports: [SharedModule], +}) +export class MetricsEndpointsRequestsComponent { + /** + * object containing service related metrics + */ + @Input() endpointsRequestsMetrics?: Services; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html new file mode 100644 index 0000000..85b8619 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.html @@ -0,0 +1,92 @@ +

Garbage collections

+ +
+
+
+ + GC Live Data Size/GC Max Data Size ({{ garbageCollectorMetrics['jvm.gc.live.data.size'] / 1048576 | number: '1.0-0' }}M / + {{ garbageCollectorMetrics['jvm.gc.max.data.size'] / 1048576 | number: '1.0-0' }}M) + + + + + {{ + (100 * garbageCollectorMetrics['jvm.gc.live.data.size']) / garbageCollectorMetrics['jvm.gc.max.data.size'] | number: '1.0-2' + }}% + + +
+
+ +
+
+ + GC Memory Promoted/GC Memory Allocated ({{ garbageCollectorMetrics['jvm.gc.memory.promoted'] / 1048576 | number: '1.0-0' }}M / + {{ garbageCollectorMetrics['jvm.gc.memory.allocated'] / 1048576 | number: '1.0-0' }}M) + + + + + {{ + (100 * garbageCollectorMetrics['jvm.gc.memory.promoted']) / garbageCollectorMetrics['jvm.gc.memory.allocated'] + | number: '1.0-2' + }}% + + +
+
+ +
+
+
Classes loaded
+
{{ garbageCollectorMetrics.classesLoaded }}
+
+
+
Classes unloaded
+
{{ garbageCollectorMetrics.classesUnloaded }}
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
CountMeanMinp50p75p95p99Max
jvm.gc.pause{{ garbageCollectorMetrics['jvm.gc.pause'].count }}{{ garbageCollectorMetrics['jvm.gc.pause'].mean | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause']['0.0'] | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause']['0.5'] | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause']['0.75'] | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause']['0.95'] | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause']['0.99'] | number: '1.0-3' }}{{ garbageCollectorMetrics['jvm.gc.pause'].max | number: '1.0-3' }}
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts new file mode 100644 index 0000000..49e397d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-garbagecollector/metrics-garbagecollector.component.ts @@ -0,0 +1,22 @@ +import { Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { GarbageCollector } from 'app/admin/metrics/metrics.model'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-garbagecollector', + templateUrl: './metrics-garbagecollector.component.html', + imports: [SharedModule], +}) +export class MetricsGarbageCollectorComponent { + /** + * object containing garbage collector related metrics + */ + @Input() garbageCollectorMetrics?: GarbageCollector; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html new file mode 100644 index 0000000..b53179b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.html @@ -0,0 +1,90 @@ + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.spec.ts new file mode 100644 index 0000000..11326f7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.spec.ts @@ -0,0 +1,325 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import { ThreadState } from '../../metrics.model'; +import { MetricsModalThreadsComponent } from './metrics-modal-threads.component'; + +describe('MetricsModalThreadsComponent', () => { + let comp: MetricsModalThreadsComponent; + let fixture: ComponentFixture; + let mockActiveModal: NgbActiveModal; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MetricsModalThreadsComponent], + providers: [NgbActiveModal], + }) + .overrideTemplate(MetricsModalThreadsComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MetricsModalThreadsComponent); + comp = fixture.componentInstance; + mockActiveModal = TestBed.inject(NgbActiveModal); + }); + + describe('ngOnInit', () => { + it('should count threads on init', () => { + // GIVEN + comp.threads = [ + { + threadName: '', + threadId: 1, + blockedTime: 1, + blockedCount: 1, + waitedTime: 1, + waitedCount: 1, + lockName: 'lock1', + lockOwnerId: 1, + lockOwnerName: 'lock1', + daemon: true, + inNative: true, + suspended: true, + threadState: ThreadState.Blocked, + priority: 1, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }, + { + threadName: '', + threadId: 2, + blockedTime: 2, + blockedCount: 2, + waitedTime: 2, + waitedCount: 2, + lockName: 'lock2', + lockOwnerId: 2, + lockOwnerName: 'lock2', + daemon: false, + inNative: false, + suspended: false, + threadState: ThreadState.Runnable, + priority: 2, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }, + { + threadName: '', + threadId: 3, + blockedTime: 3, + blockedCount: 3, + waitedTime: 3, + waitedCount: 3, + lockName: 'lock3', + lockOwnerId: 3, + lockOwnerName: 'lock3', + daemon: false, + inNative: false, + suspended: false, + threadState: ThreadState.TimedWaiting, + priority: 3, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }, + { + threadName: '', + threadId: 4, + blockedTime: 4, + blockedCount: 4, + waitedTime: 4, + waitedCount: 4, + lockName: 'lock4', + lockOwnerId: 4, + lockOwnerName: 'lock4', + daemon: false, + inNative: false, + suspended: false, + threadState: ThreadState.Waiting, + priority: 4, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }, + ]; + + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.threadDumpRunnable).toEqual(1); + expect(comp.threadDumpWaiting).toEqual(1); + expect(comp.threadDumpTimedWaiting).toEqual(1); + expect(comp.threadDumpBlocked).toEqual(1); + expect(comp.threadDumpAll).toEqual(4); + }); + }); + + describe('getBadgeClass', () => { + it('should return a success badge class for runnable thread state', () => { + // GIVEN + const threadState = ThreadState.Runnable; + + // WHEN + const badgeClass = comp.getBadgeClass(threadState); + + // THEN + expect(badgeClass).toEqual('bg-success'); + }); + + it('should return an info badge class for waiting thread state', () => { + // GIVEN + const threadState = ThreadState.Waiting; + + // WHEN + const badgeClass = comp.getBadgeClass(threadState); + + // THEN + expect(badgeClass).toEqual('bg-info'); + }); + + it('should return a warning badge class for time waiting thread state', () => { + // GIVEN + const threadState = ThreadState.TimedWaiting; + + // WHEN + const badgeClass = comp.getBadgeClass(threadState); + + // THEN + expect(badgeClass).toEqual('bg-warning'); + }); + + it('should return a danger badge class for blocked thread state', () => { + // GIVEN + const threadState = ThreadState.Blocked; + + // WHEN + const badgeClass = comp.getBadgeClass(threadState); + + // THEN + expect(badgeClass).toEqual('bg-danger'); + }); + + it('should return an empty string for others threads', () => { + // GIVEN + const threadState = ThreadState.New; + + // WHEN + const badgeClass = comp.getBadgeClass(threadState); + + // THEN + expect(badgeClass).toEqual(''); + }); + }); + + describe('getThreads', () => { + it('should return blocked threads', () => { + // GIVEN + const thread1 = { + threadName: '', + threadId: 1, + blockedTime: 1, + blockedCount: 1, + waitedTime: 1, + waitedCount: 1, + lockName: 'lock1', + lockOwnerId: 1, + lockOwnerName: 'lock1', + daemon: true, + inNative: true, + suspended: true, + threadState: ThreadState.Blocked, + priority: 1, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }; + const thread2 = { + threadName: '', + threadId: 2, + blockedTime: 2, + blockedCount: 2, + waitedTime: 2, + waitedCount: 2, + lockName: 'lock2', + lockOwnerId: 1, + lockOwnerName: 'lock2', + daemon: false, + inNative: false, + suspended: false, + threadState: ThreadState.Runnable, + priority: 2, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }; + comp.threads = [thread1, thread2]; + comp.threadStateFilter = ThreadState.Blocked; + + // WHEN + const threadsFiltered = comp.getThreads(); + + // THEN + expect(threadsFiltered).toEqual([thread1]); + }); + + it('should return an empty array of threads', () => { + // GIVEN + comp.threads = []; + comp.threadStateFilter = ThreadState.Blocked; + + // WHEN + const threadsFiltered = comp.getThreads(); + + // THEN + expect(threadsFiltered).toEqual([]); + }); + + it('should return all threads if there is no filter', () => { + // GIVEN + const thread1 = { + threadName: '', + threadId: 1, + blockedTime: 1, + blockedCount: 1, + waitedTime: 1, + waitedCount: 1, + lockName: 'lock1', + lockOwnerId: 1, + lockOwnerName: 'lock1', + daemon: true, + inNative: true, + suspended: true, + threadState: ThreadState.Blocked, + priority: 1, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }; + const thread2 = { + threadName: '', + threadId: 2, + blockedTime: 2, + blockedCount: 2, + waitedTime: 2, + waitedCount: 2, + lockName: 'lock2', + lockOwnerId: 1, + lockOwnerName: 'lock2', + daemon: false, + inNative: false, + suspended: false, + threadState: ThreadState.Runnable, + priority: 2, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }; + comp.threads = [thread1, thread2]; + comp.threadStateFilter = undefined; + + // WHEN + const threadsFiltered = comp.getThreads(); + + // THEN + expect(threadsFiltered).toEqual(comp.threads); + }); + + it('should return an empty array if there are no threads to filter', () => { + // GIVEN + comp.threads = undefined; + comp.threadStateFilter = ThreadState.Blocked; + + // WHEN + const threadsFiltered = comp.getThreads(); + + // THEN + expect(threadsFiltered).toEqual([]); + }); + }); + + describe('dismiss', () => { + it('should call dismiss function for modal on dismiss', () => { + // GIVEN + jest.spyOn(mockActiveModal, 'dismiss').mockReturnValue(undefined); + + // WHEN + comp.dismiss(); + + // THEN + expect(mockActiveModal.dismiss).toHaveBeenCalled(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts new file mode 100644 index 0000000..72c05cc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-modal-threads/metrics-modal-threads.component.ts @@ -0,0 +1,62 @@ +import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { Thread, ThreadState } from 'app/admin/metrics/metrics.model'; + +@Component({ + standalone: true, + selector: 'jhi-thread-modal', + templateUrl: './metrics-modal-threads.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SharedModule], +}) +export class MetricsModalThreadsComponent implements OnInit { + ThreadState = ThreadState; + threadStateFilter?: ThreadState; + threads?: Thread[]; + threadDumpAll = 0; + threadDumpBlocked = 0; + threadDumpRunnable = 0; + threadDumpTimedWaiting = 0; + threadDumpWaiting = 0; + + constructor(private activeModal: NgbActiveModal) {} + + ngOnInit(): void { + this.threads?.forEach(thread => { + if (thread.threadState === ThreadState.Runnable) { + this.threadDumpRunnable += 1; + } else if (thread.threadState === ThreadState.Waiting) { + this.threadDumpWaiting += 1; + } else if (thread.threadState === ThreadState.TimedWaiting) { + this.threadDumpTimedWaiting += 1; + } else if (thread.threadState === ThreadState.Blocked) { + this.threadDumpBlocked += 1; + } + }); + + this.threadDumpAll = this.threadDumpRunnable + this.threadDumpWaiting + this.threadDumpTimedWaiting + this.threadDumpBlocked; + } + + getBadgeClass(threadState: ThreadState): string { + if (threadState === ThreadState.Runnable) { + return 'bg-success'; + } else if (threadState === ThreadState.Waiting) { + return 'bg-info'; + } else if (threadState === ThreadState.TimedWaiting) { + return 'bg-warning'; + } else if (threadState === ThreadState.Blocked) { + return 'bg-danger'; + } + return ''; + } + + getThreads(): Thread[] { + return this.threads?.filter(thread => !this.threadStateFilter || thread.threadState === this.threadStateFilter) ?? []; + } + + dismiss(): void { + this.activeModal.dismiss(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.html new file mode 100644 index 0000000..275acdf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.html @@ -0,0 +1,26 @@ +

HTTP requests (time in millisecond)

+ + + + + + + + + + + + + + + + + + +
CodeCountMeanMax
{{ entry.key }} + + {{ entry.value.count }} + + + {{ filterNaN(entry.value.mean) | number: '1.0-2' }} + {{ entry.value.max | number: '1.0-2' }}
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.ts new file mode 100644 index 0000000..d19bb05 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-request/metrics-request.component.ts @@ -0,0 +1,26 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { HttpServerRequests } from 'app/admin/metrics/metrics.model'; +import { filterNaN } from 'app/core/util/operators'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-request', + templateUrl: './metrics-request.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SharedModule], +}) +export class MetricsRequestComponent { + /** + * object containing http request related metrics + */ + @Input() requestMetrics?: HttpServerRequests; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; + + filterNaN = (input: number): number => filterNaN(input); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.html new file mode 100644 index 0000000..d35be0c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.html @@ -0,0 +1,51 @@ +

System

+ + +
+
Uptime
+
{{ convertMillisecondsToDuration(systemMetrics['process.uptime']) }}
+
+ +
+
Start time
+
{{ systemMetrics['process.start.time'] | date: 'full' }}
+
+ +
+
Process CPU usage
+
{{ 100 * systemMetrics['process.cpu.usage'] | number: '1.0-2' }} %
+
+ + + {{ 100 * systemMetrics['process.cpu.usage'] | number: '1.0-2' }} % + + +
+
System CPU usage
+
{{ 100 * systemMetrics['system.cpu.usage'] | number: '1.0-2' }} %
+
+ + + {{ 100 * systemMetrics['system.cpu.usage'] | number: '1.0-2' }} % + + +
+
System CPU count
+
{{ systemMetrics['system.cpu.count'] }}
+
+ +
+
System 1m Load average
+
{{ systemMetrics['system.load.average.1m'] | number: '1.0-2' }}
+
+ +
+
Process files max
+
{{ systemMetrics['process.files.max'] | number: '1.0-0' }}
+
+ +
+
Process files open
+
{{ systemMetrics['process.files.open'] | number: '1.0-0' }}
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.ts new file mode 100644 index 0000000..1432989 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/blocks/metrics-system/metrics-system.component.ts @@ -0,0 +1,46 @@ +import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; + +import SharedModule from 'app/shared/shared.module'; +import { ProcessMetrics } from 'app/admin/metrics/metrics.model'; + +@Component({ + standalone: true, + selector: 'jhi-metrics-system', + templateUrl: './metrics-system.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [SharedModule], +}) +export class MetricsSystemComponent { + /** + * object containing thread related metrics + */ + @Input() systemMetrics?: ProcessMetrics; + + /** + * boolean field saying if the metrics are in the process of being updated + */ + @Input() updating?: boolean; + + convertMillisecondsToDuration(ms: number): string { + const times = { + year: 31557600000, + month: 2629746000, + day: 86400000, + hour: 3600000, + minute: 60000, + second: 1000, + }; + let timeString = ''; + for (const [key, value] of Object.entries(times)) { + if (Math.floor(ms / value) > 0) { + let plural = ''; + if (Math.floor(ms / value) > 1) { + plural = 's'; + } + timeString += `${Math.floor(ms / value).toString()} ${key.toString()}${plural} `; + ms = ms - value * Math.floor(ms / value); + } + } + return timeString; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.html new file mode 100644 index 0000000..c59876c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.html @@ -0,0 +1,51 @@ +
+

+ Application Metrics + + +

+ +

JVM Metrics

+ +
+ + + + + +
+ + + +
Updating...
+ + + + + + + + +
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.spec.ts new file mode 100644 index 0000000..8160dfa --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.spec.ts @@ -0,0 +1,146 @@ +import { ChangeDetectorRef } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + +import MetricsComponent from './metrics.component'; +import { MetricsService } from './metrics.service'; +import { Metrics, Thread, ThreadDump } from './metrics.model'; + +describe('MetricsComponent', () => { + let comp: MetricsComponent; + let fixture: ComponentFixture; + let service: MetricsService; + let changeDetector: ChangeDetectorRef; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, MetricsComponent], + }) + .overrideTemplate(MetricsComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MetricsComponent); + comp = fixture.componentInstance; + service = TestBed.inject(MetricsService); + changeDetector = fixture.debugElement.injector.get(ChangeDetectorRef); + }); + + describe('refresh', () => { + it('should call refresh on init', () => { + // GIVEN + const metrics = { + garbageCollector: { + 'PS Scavenge': { + collectionCount: 0, + collectionTime: 0, + }, + 'PS MarkSweep': { + collectionCount: 0, + collectionTime: 0, + }, + }, + } as unknown as Metrics; + const threadDump = { threads: [{ threadName: 'thread 1' } as Thread] } as ThreadDump; + + jest.spyOn(service, 'getMetrics').mockReturnValue(of(metrics)); + jest.spyOn(service, 'threadDump').mockReturnValue(of(threadDump)); + jest.spyOn(changeDetector.constructor.prototype, 'markForCheck'); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.getMetrics).toHaveBeenCalled(); + expect(comp.metrics).toEqual(metrics); + expect(comp.threads).toEqual(threadDump.threads); + expect(comp.updatingMetrics).toBeFalsy(); + expect(changeDetector.constructor.prototype.markForCheck).toHaveBeenCalled(); + }); + }); + + describe('metricsKeyExists', () => { + it('should check that metrics key exists', () => { + // GIVEN + comp.metrics = { + garbageCollector: { + 'PS Scavenge': { + collectionCount: 0, + collectionTime: 0, + }, + 'PS MarkSweep': { + collectionCount: 0, + collectionTime: 0, + }, + }, + } as unknown as Metrics; + + // WHEN + const garbageCollectorKeyExists = comp.metricsKeyExists('garbageCollector'); + + // THEN + expect(garbageCollectorKeyExists).toBeTruthy(); + }); + + it('should check that metrics key does not exist', () => { + // GIVEN + comp.metrics = { + garbageCollector: { + 'PS Scavenge': { + collectionCount: 0, + collectionTime: 0, + }, + 'PS MarkSweep': { + collectionCount: 0, + collectionTime: 0, + }, + }, + } as unknown as Metrics; + + // WHEN + const databasesCollectorKeyExists = comp.metricsKeyExists('databases'); + + // THEN + expect(databasesCollectorKeyExists).toBeFalsy(); + }); + }); + + describe('metricsKeyExistsAndObjectNotEmpty', () => { + it('should check that metrics key exists and is not empty', () => { + // GIVEN + comp.metrics = { + garbageCollector: { + 'PS Scavenge': { + collectionCount: 0, + collectionTime: 0, + }, + 'PS MarkSweep': { + collectionCount: 0, + collectionTime: 0, + }, + }, + } as unknown as Metrics; + + // WHEN + const garbageCollectorKeyExistsAndNotEmpty = comp.metricsKeyExistsAndObjectNotEmpty('garbageCollector'); + + // THEN + expect(garbageCollectorKeyExistsAndNotEmpty).toBeTruthy(); + }); + + it('should check that metrics key is empty', () => { + // GIVEN + comp.metrics = { + garbageCollector: {}, + } as Metrics; + + // WHEN + const garbageCollectorKeyEmpty = comp.metricsKeyExistsAndObjectNotEmpty('garbageCollector'); + + // THEN + expect(garbageCollectorKeyEmpty).toBeFalsy(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.ts new file mode 100644 index 0000000..93f7a3f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.component.ts @@ -0,0 +1,66 @@ +import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { combineLatest } from 'rxjs'; + +import SharedModule from 'app/shared/shared.module'; +import { MetricsService } from './metrics.service'; +import { Metrics, Thread } from './metrics.model'; +import { JvmMemoryComponent } from './blocks/jvm-memory/jvm-memory.component'; +import { JvmThreadsComponent } from './blocks/jvm-threads/jvm-threads.component'; +import { MetricsCacheComponent } from './blocks/metrics-cache/metrics-cache.component'; +import { MetricsDatasourceComponent } from './blocks/metrics-datasource/metrics-datasource.component'; +import { MetricsEndpointsRequestsComponent } from './blocks/metrics-endpoints-requests/metrics-endpoints-requests.component'; +import { MetricsGarbageCollectorComponent } from './blocks/metrics-garbagecollector/metrics-garbagecollector.component'; +import { MetricsModalThreadsComponent } from './blocks/metrics-modal-threads/metrics-modal-threads.component'; +import { MetricsRequestComponent } from './blocks/metrics-request/metrics-request.component'; +import { MetricsSystemComponent } from './blocks/metrics-system/metrics-system.component'; + +@Component({ + standalone: true, + selector: 'jhi-metrics', + templateUrl: './metrics.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + imports: [ + SharedModule, + JvmMemoryComponent, + JvmThreadsComponent, + MetricsCacheComponent, + MetricsDatasourceComponent, + MetricsEndpointsRequestsComponent, + MetricsGarbageCollectorComponent, + MetricsModalThreadsComponent, + MetricsRequestComponent, + MetricsSystemComponent, + ], +}) +export default class MetricsComponent implements OnInit { + metrics?: Metrics; + threads?: Thread[]; + updatingMetrics = true; + + constructor( + private metricsService: MetricsService, + private changeDetector: ChangeDetectorRef, + ) {} + + ngOnInit(): void { + this.refresh(); + } + + refresh(): void { + this.updatingMetrics = true; + combineLatest([this.metricsService.getMetrics(), this.metricsService.threadDump()]).subscribe(([metrics, threadDump]) => { + this.metrics = metrics; + this.threads = threadDump.threads; + this.updatingMetrics = false; + this.changeDetector.markForCheck(); + }); + } + + metricsKeyExists(key: keyof Metrics): boolean { + return Boolean(this.metrics?.[key]); + } + + metricsKeyExistsAndObjectNotEmpty(key: keyof Metrics): boolean { + return Boolean(this.metrics?.[key] && JSON.stringify(this.metrics[key]) !== '{}'); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.model.ts new file mode 100644 index 0000000..d9576a9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.model.ts @@ -0,0 +1,159 @@ +export interface Metrics { + jvm: { [key: string]: JvmMetrics }; + databases: Databases; + 'http.server.requests': HttpServerRequests; + cache: { [key: string]: CacheMetrics }; + garbageCollector: GarbageCollector; + services: Services; + processMetrics: ProcessMetrics; +} + +export interface JvmMetrics { + committed: number; + max: number; + used: number; +} + +export interface Databases { + min: Value; + idle: Value; + max: Value; + usage: MetricsWithPercentile; + pending: Value; + active: Value; + acquire: MetricsWithPercentile; + creation: MetricsWithPercentile; + connections: Value; +} + +export interface Value { + value: number; +} + +export interface MetricsWithPercentile { + '0.0': number; + '1.0': number; + max: number; + totalTime: number; + mean: number; + '0.5': number; + count: number; + '0.99': number; + '0.75': number; + '0.95': number; +} + +export interface HttpServerRequests { + all: { + count: number; + }; + percode: { [key: string]: MaxMeanCount }; +} + +export interface MaxMeanCount { + max: number; + mean: number; + count: number; +} + +export interface CacheMetrics { + 'cache.gets.miss': number; + 'cache.puts': number; + 'cache.gets.hit': number; + 'cache.removals': number; + 'cache.evictions': number; +} + +export interface GarbageCollector { + 'jvm.gc.max.data.size': number; + 'jvm.gc.pause': MetricsWithPercentile; + 'jvm.gc.memory.promoted': number; + 'jvm.gc.memory.allocated': number; + classesLoaded: number; + 'jvm.gc.live.data.size': number; + classesUnloaded: number; +} + +export interface Services { + [key: string]: { + [key in HttpMethod]?: MaxMeanCount; + }; +} + +export enum HttpMethod { + Post = 'POST', + Get = 'GET', + Put = 'PUT', + Patch = 'PATCH', + Delete = 'DELETE', +} + +export interface ProcessMetrics { + 'system.cpu.usage': number; + 'system.cpu.count': number; + 'system.load.average.1m'?: number; + 'process.cpu.usage': number; + 'process.files.max'?: number; + 'process.files.open'?: number; + 'process.start.time': number; + 'process.uptime': number; +} + +export interface ThreadDump { + threads: Thread[]; +} + +export interface Thread { + threadName: string; + threadId: number; + blockedTime: number; + blockedCount: number; + waitedTime: number; + waitedCount: number; + lockName: string | null; + lockOwnerId: number; + lockOwnerName: string | null; + daemon: boolean; + inNative: boolean; + suspended: boolean; + threadState: ThreadState; + priority: number; + stackTrace: StackTrace[]; + lockedMonitors: LockedMonitor[]; + lockedSynchronizers: string[]; + lockInfo: LockInfo | null; + // custom field for showing-hiding thread dump + showThreadDump?: boolean; +} + +export interface LockInfo { + className: string; + identityHashCode: number; +} + +export interface LockedMonitor { + className: string; + identityHashCode: number; + lockedStackDepth: number; + lockedStackFrame: StackTrace; +} + +export interface StackTrace { + classLoaderName: string | null; + moduleName: string | null; + moduleVersion: string | null; + methodName: string; + fileName: string; + lineNumber: number; + className: string; + nativeMethod: boolean; +} + +export enum ThreadState { + Runnable = 'RUNNABLE', + TimedWaiting = 'TIMED_WAITING', + Waiting = 'WAITING', + Blocked = 'BLOCKED', + New = 'NEW', + Terminated = 'TERMINATED', +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.spec.ts new file mode 100644 index 0000000..468ebd5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.spec.ts @@ -0,0 +1,81 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { MetricsService } from './metrics.service'; +import { ThreadDump, ThreadState } from './metrics.model'; + +describe('Logs Service', () => { + let service: MetricsService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + service = TestBed.inject(MetricsService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should return Metrics', () => { + let expectedResult; + const metrics = { + jvm: {}, + 'http.server.requests': {}, + cache: {}, + services: {}, + databases: {}, + garbageCollector: {}, + processMetrics: {}, + }; + + service.getMetrics().subscribe(received => { + expectedResult = received; + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush(metrics); + expect(expectedResult).toEqual(metrics); + }); + + it('should return Thread Dump', () => { + let expectedResult: ThreadDump | null = null; + const dump: ThreadDump = { + threads: [ + { + threadName: 'Reference Handler', + threadId: 2, + blockedTime: -1, + blockedCount: 7, + waitedTime: -1, + waitedCount: 0, + lockName: null, + lockOwnerId: -1, + lockOwnerName: null, + daemon: true, + inNative: false, + suspended: false, + threadState: ThreadState.Runnable, + priority: 10, + stackTrace: [], + lockedMonitors: [], + lockedSynchronizers: [], + lockInfo: null, + }, + ], + }; + + service.threadDump().subscribe(received => { + expectedResult = received; + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush(dump); + expect(expectedResult).toEqual(dump); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.ts new file mode 100644 index 0000000..5adb05c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/metrics/metrics.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { Metrics, ThreadDump } from './metrics.model'; + +@Injectable({ providedIn: 'root' }) +export class MetricsService { + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + getMetrics(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/jhimetrics')); + } + + threadDump(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('management/threaddump')); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.html new file mode 100644 index 0000000..0ae0c95 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.html @@ -0,0 +1,25 @@ +
+ + + + + +
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.spec.ts new file mode 100644 index 0000000..d4fc0a4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.spec.ts @@ -0,0 +1,51 @@ +jest.mock('@ng-bootstrap/ng-bootstrap'); + +import { ComponentFixture, TestBed, waitForAsync, inject, fakeAsync, tick } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import { of } from 'rxjs'; + +import { UserManagementService } from '../service/user-management.service'; + +import UserManagementDeleteDialogComponent from './user-management-delete-dialog.component'; + +describe('User Management Delete Component', () => { + let comp: UserManagementDeleteDialogComponent; + let fixture: ComponentFixture; + let service: UserManagementService; + let mockActiveModal: NgbActiveModal; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, UserManagementDeleteDialogComponent], + providers: [NgbActiveModal], + }) + .overrideTemplate(UserManagementDeleteDialogComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserManagementDeleteDialogComponent); + comp = fixture.componentInstance; + service = TestBed.inject(UserManagementService); + mockActiveModal = TestBed.inject(NgbActiveModal); + }); + + describe('confirmDelete', () => { + it('Should call delete service on confirmDelete', inject( + [], + fakeAsync(() => { + // GIVEN + jest.spyOn(service, 'delete').mockReturnValue(of({})); + + // WHEN + comp.confirmDelete('user'); + tick(); + + // THEN + expect(service.delete).toHaveBeenCalledWith('user'); + expect(mockActiveModal.close).toHaveBeenCalledWith('deleted'); + }), + )); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.ts new file mode 100644 index 0000000..382b640 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/delete/user-management-delete-dialog.component.ts @@ -0,0 +1,32 @@ +import { Component } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { User } from '../user-management.model'; +import { UserManagementService } from '../service/user-management.service'; + +@Component({ + standalone: true, + selector: 'jhi-user-mgmt-delete-dialog', + templateUrl: './user-management-delete-dialog.component.html', + imports: [SharedModule, FormsModule], +}) +export default class UserManagementDeleteDialogComponent { + user?: User; + + constructor( + private userService: UserManagementService, + private activeModal: NgbActiveModal, + ) {} + + cancel(): void { + this.activeModal.dismiss(); + } + + confirmDelete(login: string): void { + this.userService.delete(login).subscribe(() => { + this.activeModal.close('deleted'); + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.html new file mode 100644 index 0000000..10515ec --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.html @@ -0,0 +1,56 @@ +
+
+
+

+ User [{{ user.login }}] +

+ +
+
Login
+
+ {{ user.login }} + Activated + Deactivated +
+ +
First name
+
{{ user.firstName }}
+ +
Last name
+
{{ user.lastName }}
+ +
Email
+
{{ user.email }}
+ +
Language
+
{{ user.langKey }}
+ +
Created by
+
{{ user.createdBy }}
+ +
Created date
+
{{ user.createdDate | date: 'dd/MM/yy HH:mm' }}
+ +
Modified by
+
{{ user.lastModifiedBy }}
+ +
Modified date
+
{{ user.lastModifiedDate | date: 'dd/MM/yy HH:mm' }}
+ +
Profiles
+
+
    +
  • + {{ authority }} +
  • +
+
+
+ + +
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.spec.ts new file mode 100644 index 0000000..21bc612 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.spec.ts @@ -0,0 +1,56 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; + +import { Authority } from 'app/config/authority.constants'; +import { User } from '../user-management.model'; + +import UserManagementDetailComponent from './user-management-detail.component'; + +describe('User Management Detail Component', () => { + let comp: UserManagementDetailComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [UserManagementDetailComponent], + providers: [ + { + provide: ActivatedRoute, + useValue: { + data: of({ user: new User(123, 'user', 'first', 'last', 'first@last.com', true, 'en', [Authority.USER], 'admin') }), + }, + }, + ], + }) + .overrideTemplate(UserManagementDetailComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserManagementDetailComponent); + comp = fixture.componentInstance; + }); + + describe('OnInit', () => { + it('Should call load all on init', () => { + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.user).toEqual( + expect.objectContaining({ + id: 123, + login: 'user', + firstName: 'first', + lastName: 'last', + email: 'first@last.com', + activated: true, + langKey: 'en', + authorities: [Authority.USER], + createdBy: 'admin', + }), + ); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.ts new file mode 100644 index 0000000..b8a7c8f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/detail/user-management-detail.component.ts @@ -0,0 +1,23 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import SharedModule from 'app/shared/shared.module'; + +import { User } from '../user-management.model'; + +@Component({ + standalone: true, + selector: 'jhi-user-mgmt-detail', + templateUrl: './user-management-detail.component.html', + imports: [SharedModule], +}) +export default class UserManagementDetailComponent implements OnInit { + user: User | null = null; + + constructor(private route: ActivatedRoute) {} + + ngOnInit(): void { + this.route.data.subscribe(({ user }) => { + this.user = user; + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.html new file mode 100644 index 0000000..041d7d6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.html @@ -0,0 +1,124 @@ +
+

+ Users + +
+ + +
+

+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ID Login Email + Language + Profiles + Created date + + Modified by + + Modified date +
+ {{ user.id }} + {{ user.login }}{{ user.email }} + + + {{ user.langKey }} +
+ {{ authority }} +
+
{{ user.createdDate | date: 'dd/MM/yy HH:mm' }}{{ user.lastModifiedBy }}{{ user.lastModifiedDate | date: 'dd/MM/yy HH:mm' }} +
+ + + + + +
+
+
+ +
+
+ +
+ +
+ +
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.spec.ts new file mode 100644 index 0000000..ab9cbde --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.spec.ts @@ -0,0 +1,103 @@ +jest.mock('app/core/auth/account.service'); + +import { ComponentFixture, TestBed, waitForAsync, inject, fakeAsync, tick } from '@angular/core/testing'; +import { HttpHeaders, HttpResponse } from '@angular/common/http'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ActivatedRoute } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; + +import { AccountService } from 'app/core/auth/account.service'; +import { UserManagementService } from '../service/user-management.service'; +import { User } from '../user-management.model'; + +import UserManagementComponent from './user-management.component'; + +describe('User Management Component', () => { + let comp: UserManagementComponent; + let fixture: ComponentFixture; + let service: UserManagementService; + let mockAccountService: AccountService; + const data = of({ + defaultSort: 'id,asc', + }); + const queryParamMap = of( + jest.requireActual('@angular/router').convertToParamMap({ + page: '1', + size: '1', + sort: 'id,desc', + }), + ); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([]), UserManagementComponent], + providers: [{ provide: ActivatedRoute, useValue: { data, queryParamMap } }, AccountService], + }) + .overrideTemplate(UserManagementComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserManagementComponent); + comp = fixture.componentInstance; + service = TestBed.inject(UserManagementService); + mockAccountService = TestBed.inject(AccountService); + mockAccountService.identity = jest.fn(() => of(null)); + }); + + describe('OnInit', () => { + it('Should call load all on init', inject( + [], + fakeAsync(() => { + // GIVEN + const headers = new HttpHeaders().append('link', 'link;link'); + jest.spyOn(service, 'query').mockReturnValue( + of( + new HttpResponse({ + body: [new User(123)], + headers, + }), + ), + ); + + // WHEN + comp.ngOnInit(); + tick(); // simulate async + + // THEN + expect(service.query).toHaveBeenCalled(); + expect(comp.users?.[0]).toEqual(expect.objectContaining({ id: 123 })); + }), + )); + }); + + describe('setActive', () => { + it('Should update user and call load all', inject( + [], + fakeAsync(() => { + // GIVEN + const headers = new HttpHeaders().append('link', 'link;link'); + const user = new User(123); + jest.spyOn(service, 'query').mockReturnValue( + of( + new HttpResponse({ + body: [user], + headers, + }), + ), + ); + jest.spyOn(service, 'update').mockReturnValue(of(user)); + + // WHEN + comp.setActive(user, true); + tick(); // simulate async + + // THEN + expect(service.update).toHaveBeenCalledWith({ ...user, activated: true }); + expect(service.query).toHaveBeenCalled(); + expect(comp.users?.[0]).toEqual(expect.objectContaining({ id: 123 })); + }), + )); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.ts new file mode 100644 index 0000000..96b3e9d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/list/user-management.component.ts @@ -0,0 +1,116 @@ +import { Component, OnInit } from '@angular/core'; +import { RouterModule, ActivatedRoute, Router } from '@angular/router'; +import { HttpResponse, HttpHeaders } from '@angular/common/http'; +import { combineLatest } from 'rxjs'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import SharedModule from 'app/shared/shared.module'; +import { SortDirective, SortByDirective } from 'app/shared/sort'; +import { ITEMS_PER_PAGE } from 'app/config/pagination.constants'; +import { ASC, DESC, SORT } from 'app/config/navigation.constants'; +import { ItemCountComponent } from 'app/shared/pagination'; +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; +import { UserManagementService } from '../service/user-management.service'; +import { User } from '../user-management.model'; +import UserManagementDeleteDialogComponent from '../delete/user-management-delete-dialog.component'; + +@Component({ + standalone: true, + selector: 'jhi-user-mgmt', + templateUrl: './user-management.component.html', + imports: [RouterModule, SharedModule, SortDirective, SortByDirective, UserManagementDeleteDialogComponent, ItemCountComponent], +}) +export default class UserManagementComponent implements OnInit { + currentAccount: Account | null = null; + users: User[] | null = null; + isLoading = false; + totalItems = 0; + itemsPerPage = ITEMS_PER_PAGE; + page!: number; + predicate!: string; + ascending!: boolean; + + constructor( + private userService: UserManagementService, + private accountService: AccountService, + private activatedRoute: ActivatedRoute, + private router: Router, + private modalService: NgbModal, + ) {} + + ngOnInit(): void { + this.accountService.identity().subscribe(account => (this.currentAccount = account)); + this.handleNavigation(); + } + + setActive(user: User, isActivated: boolean): void { + this.userService.update({ ...user, activated: isActivated }).subscribe(() => this.loadAll()); + } + + trackIdentity(_index: number, item: User): number { + return item.id!; + } + + deleteUser(user: User): void { + const modalRef = this.modalService.open(UserManagementDeleteDialogComponent, { size: 'lg', backdrop: 'static' }); + modalRef.componentInstance.user = user; + // unsubscribe not needed because closed completes on modal close + modalRef.closed.subscribe(reason => { + if (reason === 'deleted') { + this.loadAll(); + } + }); + } + + loadAll(): void { + this.isLoading = true; + this.userService + .query({ + page: this.page - 1, + size: this.itemsPerPage, + sort: this.sort(), + }) + .subscribe({ + next: (res: HttpResponse) => { + this.isLoading = false; + this.onSuccess(res.body, res.headers); + }, + error: () => (this.isLoading = false), + }); + } + + transition(): void { + this.router.navigate(['./'], { + relativeTo: this.activatedRoute.parent, + queryParams: { + page: this.page, + sort: `${this.predicate},${this.ascending ? ASC : DESC}`, + }, + }); + } + + private handleNavigation(): void { + combineLatest([this.activatedRoute.data, this.activatedRoute.queryParamMap]).subscribe(([data, params]) => { + const page = params.get('page'); + this.page = +(page ?? 1); + const sort = (params.get(SORT) ?? data['defaultSort']).split(','); + this.predicate = sort[0]; + this.ascending = sort[1] === ASC; + this.loadAll(); + }); + } + + private sort(): string[] { + const result = [`${this.predicate},${this.ascending ? ASC : DESC}`]; + if (this.predicate !== 'id') { + result.push('id'); + } + return result; + } + + private onSuccess(users: User[] | null, headers: HttpHeaders): void { + this.totalItems = Number(headers.get('X-Total-Count')); + this.users = users; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.spec.ts new file mode 100644 index 0000000..41a6ec8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.spec.ts @@ -0,0 +1,67 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { Authority } from 'app/config/authority.constants'; +import { User } from '../user-management.model'; + +import { UserManagementService } from './user-management.service'; + +describe('User Service', () => { + let service: UserManagementService; + let httpMock: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + service = TestBed.inject(UserManagementService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should return User', () => { + let expectedResult: string | undefined; + + service.find('user').subscribe(received => { + expectedResult = received.login; + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush(new User(123, 'user')); + expect(expectedResult).toEqual('user'); + }); + + it('should return Authorities', () => { + let expectedResult: string[] = []; + + service.authorities().subscribe(authorities => { + expectedResult = authorities; + }); + const req = httpMock.expectOne({ method: 'GET' }); + + req.flush([Authority.USER, Authority.ADMIN]); + expect(expectedResult).toEqual([Authority.USER, Authority.ADMIN]); + }); + + it('should propagate not found response', () => { + let expectedResult = 0; + + service.find('user').subscribe({ + error: (error: HttpErrorResponse) => (expectedResult = error.status), + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush('Invalid request parameters', { + status: 404, + statusText: 'Bad Request', + }); + expect(expectedResult).toEqual(404); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.ts new file mode 100644 index 0000000..fda617a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/service/user-management.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { createRequestOption } from 'app/core/request/request-util'; +import { Pagination } from 'app/core/request/request.model'; +import { IUser } from '../user-management.model'; + +@Injectable({ providedIn: 'root' }) +export class UserManagementService { + private resourceUrl = this.applicationConfigService.getEndpointFor('api/admin/users'); + + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + create(user: IUser): Observable { + return this.http.post(this.resourceUrl, user); + } + + update(user: IUser): Observable { + return this.http.put(this.resourceUrl, user); + } + + find(login: string): Observable { + return this.http.get(`${this.resourceUrl}/${login}`); + } + + query(req?: Pagination): Observable> { + const options = createRequestOption(req); + return this.http.get(this.resourceUrl, { params: options, observe: 'response' }); + } + + delete(login: string): Observable<{}> { + return this.http.delete(`${this.resourceUrl}/${login}`); + } + + authorities(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('api/authorities')); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.html new file mode 100644 index 0000000..7f739ba --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.html @@ -0,0 +1,136 @@ +
+
+
+

Create or edit a user

+ + + +
+ + +
+ +
+ + + +
+ This field is required. + + This field cannot be longer than 50 characters. + + This field can only contain letters, digits and e-mail addresses. +
+
+ +
+ + + +
+ This field cannot be longer than 50 characters. +
+
+ +
+ + + +
+ This field cannot be longer than 50 characters. +
+
+ +
+ + + +
+ This field is required. + + This field cannot be longer than 100 characters. + + This field is required to be at least 5 characters. + + Your email is invalid. +
+
+ +
+ +
+ +
+ + +
+ +
+ + +
+ + + +
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.spec.ts new file mode 100644 index 0000000..199eb49 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.spec.ts @@ -0,0 +1,94 @@ +import { ComponentFixture, TestBed, waitForAsync, inject, fakeAsync, tick } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { FormBuilder } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { of } from 'rxjs'; + +import { Authority } from 'app/config/authority.constants'; +import { UserManagementService } from '../service/user-management.service'; +import { User } from '../user-management.model'; + +import UserManagementUpdateComponent from './user-management-update.component'; + +describe('User Management Update Component', () => { + let comp: UserManagementUpdateComponent; + let fixture: ComponentFixture; + let service: UserManagementService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, UserManagementUpdateComponent], + providers: [ + FormBuilder, + { + provide: ActivatedRoute, + useValue: { + data: of({ user: new User(123, 'user', 'first', 'last', 'first@last.com', true, 'en', [Authority.USER], 'admin') }), + }, + }, + ], + }) + .overrideTemplate(UserManagementUpdateComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(UserManagementUpdateComponent); + comp = fixture.componentInstance; + service = TestBed.inject(UserManagementService); + }); + + describe('OnInit', () => { + it('Should load authorities and language on init', inject( + [], + fakeAsync(() => { + // GIVEN + jest.spyOn(service, 'authorities').mockReturnValue(of(['USER'])); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(service.authorities).toHaveBeenCalled(); + expect(comp.authorities).toEqual(['USER']); + }), + )); + }); + + describe('save', () => { + it('Should call update service on save for existing user', inject( + [], + fakeAsync(() => { + // GIVEN + const entity = { id: 123 }; + jest.spyOn(service, 'update').mockReturnValue(of(entity)); + comp.editForm.patchValue(entity); + // WHEN + comp.save(); + tick(); // simulate async + + // THEN + expect(service.update).toHaveBeenCalledWith(expect.objectContaining(entity)); + expect(comp.isSaving).toEqual(false); + }), + )); + + it('Should call create service on save for new user', inject( + [], + fakeAsync(() => { + // GIVEN + const entity = { login: 'foo' } as User; + jest.spyOn(service, 'create').mockReturnValue(of(entity)); + comp.editForm.patchValue(entity); + // WHEN + comp.save(); + tick(); // simulate async + + // THEN + expect(comp.editForm.getRawValue().id).toBeNull(); + expect(service.create).toHaveBeenCalledWith(expect.objectContaining(entity)); + expect(comp.isSaving).toEqual(false); + }), + )); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts new file mode 100644 index 0000000..660629b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/update/user-management-update.component.ts @@ -0,0 +1,94 @@ +import { Component, OnInit } from '@angular/core'; +import { FormGroup, FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; + +import SharedModule from 'app/shared/shared.module'; +import { LANGUAGES } from 'app/config/language.constants'; +import { IUser } from '../user-management.model'; +import { UserManagementService } from '../service/user-management.service'; + +const userTemplate = {} as IUser; + +const newUser: IUser = { + langKey: 'en', + activated: true, +} as IUser; + +@Component({ + standalone: true, + selector: 'jhi-user-mgmt-update', + templateUrl: './user-management-update.component.html', + imports: [SharedModule, FormsModule, ReactiveFormsModule], +}) +export default class UserManagementUpdateComponent implements OnInit { + languages = LANGUAGES; + authorities: string[] = []; + isSaving = false; + + editForm = new FormGroup({ + id: new FormControl(userTemplate.id), + login: new FormControl(userTemplate.login, { + nonNullable: true, + validators: [ + Validators.required, + Validators.minLength(1), + Validators.maxLength(50), + Validators.pattern('^[a-zA-Z0-9!$&*+=?^_`{|}~.-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$|^[_.@A-Za-z0-9-]+$'), + ], + }), + firstName: new FormControl(userTemplate.firstName, { validators: [Validators.maxLength(50)] }), + lastName: new FormControl(userTemplate.lastName, { validators: [Validators.maxLength(50)] }), + email: new FormControl(userTemplate.email, { + nonNullable: true, + validators: [Validators.minLength(5), Validators.maxLength(254), Validators.email], + }), + activated: new FormControl(userTemplate.activated, { nonNullable: true }), + langKey: new FormControl(userTemplate.langKey, { nonNullable: true }), + authorities: new FormControl(userTemplate.authorities, { nonNullable: true }), + }); + + constructor( + private userService: UserManagementService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.data.subscribe(({ user }) => { + if (user) { + this.editForm.reset(user); + } else { + this.editForm.reset(newUser); + } + }); + this.userService.authorities().subscribe(authorities => (this.authorities = authorities)); + } + + previousState(): void { + window.history.back(); + } + + save(): void { + this.isSaving = true; + const user = this.editForm.getRawValue(); + if (user.id !== null) { + this.userService.update(user).subscribe({ + next: () => this.onSaveSuccess(), + error: () => this.onSaveError(), + }); + } else { + this.userService.create(user).subscribe({ + next: () => this.onSaveSuccess(), + error: () => this.onSaveError(), + }); + } + } + + private onSaveSuccess(): void { + this.isSaving = false; + this.previousState(); + } + + private onSaveError(): void { + this.isSaving = false; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.model.ts new file mode 100644 index 0000000..bdb2844 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.model.ts @@ -0,0 +1,31 @@ +export interface IUser { + id: number | null; + login?: string; + firstName?: string | null; + lastName?: string | null; + email?: string; + activated?: boolean; + langKey?: string; + authorities?: string[]; + createdBy?: string; + createdDate?: Date; + lastModifiedBy?: string; + lastModifiedDate?: Date; +} + +export class User implements IUser { + constructor( + public id: number | null, + public login?: string, + public firstName?: string | null, + public lastName?: string | null, + public email?: string, + public activated?: boolean, + public langKey?: string, + public authorities?: string[], + public createdBy?: string, + public createdDate?: Date, + public lastModifiedBy?: string, + public lastModifiedDate?: Date, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.route.ts new file mode 100644 index 0000000..1a8f764 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/admin/user-management/user-management.route.ts @@ -0,0 +1,50 @@ +import { inject } from '@angular/core'; +import { ActivatedRouteSnapshot, Routes, ResolveFn } from '@angular/router'; +import { of } from 'rxjs'; + +import { IUser } from './user-management.model'; +import { UserManagementService } from './service/user-management.service'; +import UserManagementComponent from './list/user-management.component'; +import UserManagementDetailComponent from './detail/user-management-detail.component'; +import UserManagementUpdateComponent from './update/user-management-update.component'; + +export const UserManagementResolve: ResolveFn = (route: ActivatedRouteSnapshot) => { + const login = route.paramMap.get('login'); + if (login) { + return inject(UserManagementService).find(login); + } + return of(null); +}; + +const userManagementRoute: Routes = [ + { + path: '', + component: UserManagementComponent, + data: { + defaultSort: 'id,asc', + }, + }, + { + path: ':login/view', + component: UserManagementDetailComponent, + resolve: { + user: UserManagementResolve, + }, + }, + { + path: 'new', + component: UserManagementUpdateComponent, + resolve: { + user: UserManagementResolve, + }, + }, + { + path: ':login/edit', + component: UserManagementUpdateComponent, + resolve: { + user: UserManagementResolve, + }, + }, +]; + +export default userManagementRoute; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-page-title-strategy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-page-title-strategy.ts new file mode 100644 index 0000000..afe66a4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-page-title-strategy.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { RouterStateSnapshot, TitleStrategy } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; + +@Injectable() +export class AppPageTitleStrategy extends TitleStrategy { + constructor(private translateService: TranslateService) { + super(); + } + + override updateTitle(routerState: RouterStateSnapshot): void { + let pageTitle = this.buildTitle(routerState); + if (!pageTitle) { + pageTitle = 'global.title'; + } + this.translateService.get(pageTitle).subscribe(title => { + document.title = title; + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-routing.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-routing.module.ts new file mode 100644 index 0000000..a076458 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app-routing.module.ts @@ -0,0 +1,55 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import { DEBUG_INFO_ENABLED } from 'app/app.constants'; +import { Authority } from 'app/config/authority.constants'; +import { UserRouteAccessService } from 'app/core/auth/user-route-access.service'; +import { errorRoute } from './layouts/error/error.route'; + +import HomeComponent from './home/home.component'; +import NavbarComponent from './layouts/navbar/navbar.component'; +import LoginComponent from './login/login.component'; + +@NgModule({ + imports: [ + RouterModule.forRoot( + [ + { + path: '', + component: HomeComponent, + title: 'home.title', + }, + { + path: '', + component: NavbarComponent, + outlet: 'navbar', + }, + { + path: 'admin', + data: { + authorities: [Authority.ADMIN], + }, + canActivate: [UserRouteAccessService], + loadChildren: () => import('./admin/admin-routing.module'), + }, + { + path: 'account', + loadChildren: () => import('./account/account.route'), + }, + { + path: 'login', + component: LoginComponent, + title: 'login.title', + }, + { + path: '', + loadChildren: () => import(`./entities/entity-routing.module`).then(({ EntityRoutingModule }) => EntityRoutingModule), + }, + ...errorRoute, + ], + { enableTracing: DEBUG_INFO_ENABLED, bindToComponentInputs: true }, + ), + ], + exports: [RouterModule], +}) +export class AppRoutingModule {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.constants.ts new file mode 100644 index 0000000..695015c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.constants.ts @@ -0,0 +1,9 @@ +// These constants are injected via webpack DefinePlugin variables. +// You can add more variables in webpack.common.js or in profile specific webpack..js files. +// If you change the values in the webpack config files, you need to re run webpack to update the application + +declare const __DEBUG_INFO_ENABLED__: boolean; +declare const __VERSION__: string; + +export const VERSION = __VERSION__; +export const DEBUG_INFO_ENABLED = __DEBUG_INFO_ENABLED__; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.module.ts new file mode 100644 index 0000000..6823ff0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/app.module.ts @@ -0,0 +1,51 @@ +import { NgModule, LOCALE_ID } from '@angular/core'; +import { registerLocaleData } from '@angular/common'; +import { HttpClientModule } from '@angular/common/http'; +import locale from '@angular/common/locales/en'; +import { BrowserModule, Title } from '@angular/platform-browser'; +import { TitleStrategy } from '@angular/router'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { FaIconLibrary } from '@fortawesome/angular-fontawesome'; +import dayjs from 'dayjs/esm'; +import { NgbDateAdapter, NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import './config/dayjs'; +import { TranslationModule } from 'app/shared/language/translation.module'; +import { httpInterceptorProviders } from 'app/core/interceptor/index'; +import { AppRoutingModule } from './app-routing.module'; +// jhipster-needle-angular-add-module-import JHipster will add new module here +import { NgbDateDayjsAdapter } from './config/datepicker-adapter'; +import { fontAwesomeIcons } from './config/font-awesome-icons'; +import MainComponent from './layouts/main/main.component'; +import MainModule from './layouts/main/main.module'; +import { AppPageTitleStrategy } from './app-page-title-strategy'; + +@NgModule({ + imports: [ + BrowserModule, + // jhipster-needle-angular-add-module JHipster will add new module here + AppRoutingModule, + // Set this to true to enable service worker (PWA) + ServiceWorkerModule.register('ngsw-worker.js', { enabled: false }), + HttpClientModule, + MainModule, + TranslationModule, + ], + providers: [ + Title, + { provide: LOCALE_ID, useValue: 'en' }, + { provide: NgbDateAdapter, useClass: NgbDateDayjsAdapter }, + httpInterceptorProviders, + { provide: TitleStrategy, useClass: AppPageTitleStrategy }, + ], + bootstrap: [MainComponent], +}) +export class AppModule { + constructor(applicationConfigService: ApplicationConfigService, iconLibrary: FaIconLibrary, dpConfig: NgbDatepickerConfig) { + applicationConfigService.setEndpointPrefix(SERVER_API_URL); + registerLocaleData(locale); + iconLibrary.addIcons(...fontAwesomeIcons); + dpConfig.minDate = { year: dayjs().subtract(100, 'year').year(), month: 1, day: 1 }; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/authority.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/authority.constants.ts new file mode 100644 index 0000000..1501bcf --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/authority.constants.ts @@ -0,0 +1,4 @@ +export enum Authority { + ADMIN = 'ROLE_ADMIN', + USER = 'ROLE_USER', +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/datepicker-adapter.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/datepicker-adapter.ts new file mode 100644 index 0000000..3f8b16c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/datepicker-adapter.ts @@ -0,0 +1,20 @@ +/** + * Angular bootstrap Date adapter + */ +import { Injectable } from '@angular/core'; +import { NgbDateAdapter, NgbDateStruct } from '@ng-bootstrap/ng-bootstrap'; +import dayjs from 'dayjs/esm'; + +@Injectable() +export class NgbDateDayjsAdapter extends NgbDateAdapter { + fromModel(date: dayjs.Dayjs | null): NgbDateStruct | null { + if (date && dayjs.isDayjs(date) && date.isValid()) { + return { year: date.year(), month: date.month() + 1, day: date.date() }; + } + return null; + } + + toModel(date: NgbDateStruct | null): dayjs.Dayjs | null { + return date ? dayjs(`${date.year}-${date.month}-${date.day}`) : null; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/dayjs.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/dayjs.ts new file mode 100644 index 0000000..e29f5a0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/dayjs.ts @@ -0,0 +1,14 @@ +import dayjs from 'dayjs/esm'; +import customParseFormat from 'dayjs/esm/plugin/customParseFormat'; +import duration from 'dayjs/esm/plugin/duration'; +import relativeTime from 'dayjs/esm/plugin/relativeTime'; + +// jhipster-needle-i18n-language-dayjs-imports - JHipster will import languages from dayjs here +import 'dayjs/esm/locale/en'; +import 'dayjs/esm/locale/fr'; +import 'dayjs/esm/locale/de'; + +// DAYJS CONFIGURATION +dayjs.extend(customParseFormat); +dayjs.extend(duration); +dayjs.extend(relativeTime); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/error.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/error.constants.ts new file mode 100644 index 0000000..eff19a3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/error.constants.ts @@ -0,0 +1,3 @@ +export const PROBLEM_BASE_URL = 'https://www.jhipster.tech/problem'; +export const EMAIL_ALREADY_USED_TYPE = `${PROBLEM_BASE_URL}/email-already-used`; +export const LOGIN_ALREADY_USED_TYPE = `${PROBLEM_BASE_URL}/login-already-used`; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/font-awesome-icons.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/font-awesome-icons.ts new file mode 100644 index 0000000..7fcf169 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/font-awesome-icons.ts @@ -0,0 +1,83 @@ +import { + faArrowLeft, + faAsterisk, + faBan, + faBars, + faBell, + faBook, + faCalendarAlt, + faCheck, + faCloud, + faCogs, + faDatabase, + faEye, + faFlag, + faHeart, + faHome, + faList, + faLock, + faPencilAlt, + faPlus, + faRoad, + faSave, + faSearch, + faSignOutAlt, + faSignInAlt, + faSort, + faSortDown, + faSortUp, + faSync, + faTachometerAlt, + faTasks, + faThList, + faTimes, + faTrashAlt, + faUser, + faUserPlus, + faUsers, + faUsersCog, + faWrench, + // jhipster-needle-add-icon-import +} from '@fortawesome/free-solid-svg-icons'; + +export const fontAwesomeIcons = [ + faArrowLeft, + faAsterisk, + faBan, + faBars, + faBell, + faBook, + faCalendarAlt, + faCheck, + faCloud, + faCogs, + faDatabase, + faEye, + faFlag, + faHeart, + faHome, + faList, + faLock, + faPencilAlt, + faPlus, + faRoad, + faSave, + faSearch, + faSignOutAlt, + faSignInAlt, + faSort, + faSortDown, + faSortUp, + faSync, + faTachometerAlt, + faTasks, + faThList, + faTimes, + faTrashAlt, + faUser, + faUserPlus, + faUsers, + faUsersCog, + faWrench, + // jhipster-needle-add-icon-import +]; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/input.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/input.constants.ts new file mode 100644 index 0000000..1e3978a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/input.constants.ts @@ -0,0 +1,2 @@ +export const DATE_FORMAT = 'YYYY-MM-DD'; +export const DATE_TIME_FORMAT = 'YYYY-MM-DDTHH:mm'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/language.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/language.constants.ts new file mode 100644 index 0000000..52534f2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/language.constants.ts @@ -0,0 +1,10 @@ +/* + Languages codes are ISO_639-1 codes, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes + They are written in English to avoid character encoding issues (not a perfect solution) +*/ +export const LANGUAGES: string[] = [ + 'en', + 'fr', + 'de', + // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array +]; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/navigation.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/navigation.constants.ts new file mode 100644 index 0000000..609160d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/navigation.constants.ts @@ -0,0 +1,5 @@ +export const ASC = 'asc'; +export const DESC = 'desc'; +export const SORT = 'sort'; +export const ITEM_DELETED_EVENT = 'deleted'; +export const DEFAULT_SORT_DATA = 'defaultSort'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/pagination.constants.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/pagination.constants.ts new file mode 100644 index 0000000..6bee3ff --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/pagination.constants.ts @@ -0,0 +1,3 @@ +export const TOTAL_COUNT_RESPONSE_HEADER = 'X-Total-Count'; +export const PAGE_HEADER = 'page'; +export const ITEMS_PER_PAGE = 20; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/translation.config.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/translation.config.ts new file mode 100644 index 0000000..b5ac129 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/translation.config.ts @@ -0,0 +1,20 @@ +import { HttpClient } from '@angular/common/http'; +import { MissingTranslationHandler, MissingTranslationHandlerParams, TranslateLoader } from '@ngx-translate/core'; +import { TranslateHttpLoader } from '@ngx-translate/http-loader'; + +export const translationNotFoundMessage = 'translation-not-found'; + +export class MissingTranslationHandlerImpl implements MissingTranslationHandler { + handle(params: MissingTranslationHandlerParams): string { + const key = params.key; + return `${translationNotFoundMessage}[${key}]`; + } +} + +export function translatePartialLoader(http: HttpClient): TranslateLoader { + return new TranslateHttpLoader(http, 'i18n/', `.json?_=${I18N_HASH}`); +} + +export function missingTranslationHandler(): MissingTranslationHandler { + return new MissingTranslationHandlerImpl(); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/uib-pagination.config.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/uib-pagination.config.ts new file mode 100644 index 0000000..ecabe16 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/config/uib-pagination.config.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { NgbPaginationConfig } from '@ng-bootstrap/ng-bootstrap'; + +import { ITEMS_PER_PAGE } from 'app/config/pagination.constants'; + +@Injectable({ providedIn: 'root' }) +export class PaginationConfig { + constructor(config: NgbPaginationConfig) { + config.boundaryLinks = true; + config.maxSize = 5; + config.pageSize = ITEMS_PER_PAGE; + config.size = 'sm'; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.model.ts new file mode 100644 index 0000000..76e6763 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.model.ts @@ -0,0 +1,12 @@ +export class Account { + constructor( + public activated: boolean, + public authorities: string[], + public email: string, + public firstName: string | null, + public langKey: string, + public lastName: string | null, + public login: string, + public imageUrl: string | null, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.spec.ts new file mode 100644 index 0000000..7ccd0aa --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.spec.ts @@ -0,0 +1,248 @@ +jest.mock('app/core/auth/state-storage.service'); + +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; +import { of } from 'rxjs'; + +import { Account } from 'app/core/auth/account.model'; +import { Authority } from 'app/config/authority.constants'; +import { StateStorageService } from 'app/core/auth/state-storage.service'; +import { ApplicationConfigService } from 'app/core/config/application-config.service'; + +import { AccountService } from './account.service'; + +function accountWithAuthorities(authorities: string[]): Account { + return { + activated: true, + authorities, + email: '', + firstName: '', + langKey: '', + lastName: '', + login: '', + imageUrl: '', + }; +} + +describe('Account Service', () => { + let service: AccountService; + let applicationConfigService: ApplicationConfigService; + let httpMock: HttpTestingController; + let mockStorageService: StateStorageService; + let mockRouter: Router; + let mockTranslateService: TranslateService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot()], + providers: [StateStorageService], + }); + + service = TestBed.inject(AccountService); + applicationConfigService = TestBed.inject(ApplicationConfigService); + httpMock = TestBed.inject(HttpTestingController); + mockStorageService = TestBed.inject(StateStorageService); + mockRouter = TestBed.inject(Router); + jest.spyOn(mockRouter, 'navigateByUrl').mockImplementation(() => Promise.resolve(true)); + + mockTranslateService = TestBed.inject(TranslateService); + jest.spyOn(mockTranslateService, 'use').mockImplementation(() => of('')); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('save', () => { + it('should call account saving endpoint with correct values', () => { + // GIVEN + const account = accountWithAuthorities([]); + + // WHEN + service.save(account).subscribe(); + const testRequest = httpMock.expectOne({ method: 'POST', url: applicationConfigService.getEndpointFor('api/account') }); + testRequest.flush({}); + + // THEN + expect(testRequest.request.body).toEqual(account); + }); + }); + + describe('authenticate', () => { + it('authenticationState should emit null if input is null', () => { + // GIVEN + let userIdentity: Account | null = accountWithAuthorities([]); + service.getAuthenticationState().subscribe(account => (userIdentity = account)); + + // WHEN + service.authenticate(null); + + // THEN + expect(userIdentity).toBeNull(); + expect(service.isAuthenticated()).toBe(false); + }); + + it('authenticationState should emit the same account as was in input parameter', () => { + // GIVEN + const expectedResult = accountWithAuthorities([]); + let userIdentity: Account | null = null; + service.getAuthenticationState().subscribe(account => (userIdentity = account)); + + // WHEN + service.authenticate(expectedResult); + + // THEN + expect(userIdentity).toEqual(expectedResult); + expect(service.isAuthenticated()).toBe(true); + }); + }); + + describe('identity', () => { + it('should call /account only once if last call have not returned', () => { + // When I call + service.identity().subscribe(); + // Once more + service.identity().subscribe(); + // Then there is only request + httpMock.expectOne({ method: 'GET' }); + }); + + it('should call /account only once if not logged out after first authentication and should call /account again if user has logged out', () => { + // Given the user is authenticated + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).flush({}); + + // When I call + service.identity().subscribe(); + + // Then there is no second request + httpMock.expectNone({ method: 'GET' }); + + // When I log out + service.authenticate(null); + // and then call + service.identity().subscribe(); + + // Then there is a new request + httpMock.expectOne({ method: 'GET' }); + }); + + describe('should change the language on authentication if necessary', () => { + it('should change language if user has not changed language manually', () => { + // GIVEN + mockStorageService.getLocale = jest.fn(() => null); + + // WHEN + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).flush({ ...accountWithAuthorities([]), langKey: 'accountLang' }); + + // THEN + expect(mockTranslateService.use).toHaveBeenCalledWith('accountLang'); + }); + + it('should not change language if user has changed language manually', () => { + // GIVEN + mockStorageService.getLocale = jest.fn(() => 'sessionLang'); + + // WHEN + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).flush({ ...accountWithAuthorities([]), langKey: 'accountLang' }); + + // THEN + expect(mockTranslateService.use).not.toHaveBeenCalled(); + }); + }); + + describe('navigateToStoredUrl', () => { + it('should navigate to the previous stored url post successful authentication', () => { + // GIVEN + mockStorageService.getUrl = jest.fn(() => 'admin/users?page=0'); + + // WHEN + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).flush({}); + + // THEN + expect(mockStorageService.getUrl).toHaveBeenCalledTimes(1); + expect(mockStorageService.clearUrl).toHaveBeenCalledTimes(1); + expect(mockRouter.navigateByUrl).toHaveBeenCalledWith('admin/users?page=0'); + }); + + it('should not navigate to the previous stored url when authentication fails', () => { + // WHEN + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).error(new ErrorEvent('')); + + // THEN + expect(mockStorageService.getUrl).not.toHaveBeenCalled(); + expect(mockStorageService.clearUrl).not.toHaveBeenCalled(); + expect(mockRouter.navigateByUrl).not.toHaveBeenCalled(); + }); + + it('should not navigate to the previous stored url when no such url exists post successful authentication', () => { + // GIVEN + mockStorageService.getUrl = jest.fn(() => null); + + // WHEN + service.identity().subscribe(); + httpMock.expectOne({ method: 'GET' }).flush({}); + + // THEN + expect(mockStorageService.getUrl).toHaveBeenCalledTimes(1); + expect(mockStorageService.clearUrl).not.toHaveBeenCalled(); + expect(mockRouter.navigateByUrl).not.toHaveBeenCalled(); + }); + }); + }); + + describe('hasAnyAuthority', () => { + describe('hasAnyAuthority string parameter', () => { + it('should return false if user is not logged', () => { + const hasAuthority = service.hasAnyAuthority(Authority.USER); + expect(hasAuthority).toBe(false); + }); + + it('should return false if user is logged and has not authority', () => { + service.authenticate(accountWithAuthorities([Authority.USER])); + + const hasAuthority = service.hasAnyAuthority(Authority.ADMIN); + + expect(hasAuthority).toBe(false); + }); + + it('should return true if user is logged and has authority', () => { + service.authenticate(accountWithAuthorities([Authority.USER])); + + const hasAuthority = service.hasAnyAuthority(Authority.USER); + + expect(hasAuthority).toBe(true); + }); + }); + + describe('hasAnyAuthority array parameter', () => { + it('should return false if user is not logged', () => { + const hasAuthority = service.hasAnyAuthority([Authority.USER]); + expect(hasAuthority).toBeFalsy(); + }); + + it('should return false if user is logged and has not authority', () => { + service.authenticate(accountWithAuthorities([Authority.USER])); + + const hasAuthority = service.hasAnyAuthority([Authority.ADMIN]); + + expect(hasAuthority).toBe(false); + }); + + it('should return true if user is logged and has authority', () => { + service.authenticate(accountWithAuthorities([Authority.USER])); + + const hasAuthority = service.hasAnyAuthority([Authority.USER, Authority.ADMIN]); + + expect(hasAuthority).toBe(true); + }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.ts new file mode 100644 index 0000000..b58ad26 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/account.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import { Router } from '@angular/router'; +import { HttpClient } from '@angular/common/http'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, ReplaySubject, of } from 'rxjs'; +import { shareReplay, tap, catchError } from 'rxjs/operators'; + +import { StateStorageService } from 'app/core/auth/state-storage.service'; +import { Account } from 'app/core/auth/account.model'; +import { ApplicationConfigService } from '../config/application-config.service'; + +@Injectable({ providedIn: 'root' }) +export class AccountService { + private userIdentity: Account | null = null; + private authenticationState = new ReplaySubject(1); + private accountCache$?: Observable | null; + + constructor( + private translateService: TranslateService, + private http: HttpClient, + private stateStorageService: StateStorageService, + private router: Router, + private applicationConfigService: ApplicationConfigService, + ) {} + + save(account: Account): Observable<{}> { + return this.http.post(this.applicationConfigService.getEndpointFor('api/account'), account); + } + + authenticate(identity: Account | null): void { + this.userIdentity = identity; + this.authenticationState.next(this.userIdentity); + if (!identity) { + this.accountCache$ = null; + } + } + + hasAnyAuthority(authorities: string[] | string): boolean { + if (!this.userIdentity) { + return false; + } + if (!Array.isArray(authorities)) { + authorities = [authorities]; + } + return this.userIdentity.authorities.some((authority: string) => authorities.includes(authority)); + } + + identity(force?: boolean): Observable { + if (!this.accountCache$ || force) { + this.accountCache$ = this.fetch().pipe( + tap((account: Account) => { + this.authenticate(account); + + // After retrieve the account info, the language will be changed to + // the user's preferred language configured in the account setting + // unless user have choosed other language in the current session + if (!this.stateStorageService.getLocale()) { + this.translateService.use(account.langKey); + } + + this.navigateToStoredUrl(); + }), + shareReplay(), + ); + } + return this.accountCache$.pipe(catchError(() => of(null))); + } + + isAuthenticated(): boolean { + return this.userIdentity !== null; + } + + getAuthenticationState(): Observable { + return this.authenticationState.asObservable(); + } + + private fetch(): Observable { + return this.http.get(this.applicationConfigService.getEndpointFor('api/account')); + } + + private navigateToStoredUrl(): void { + // previousState can be set in the authExpiredInterceptor and in the userRouteAccessService + // if login is successful, go to stored previousState and clear previousState + const previousUrl = this.stateStorageService.getUrl(); + if (previousUrl) { + this.stateStorageService.clearUrl(); + this.router.navigateByUrl(previousUrl); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.spec.ts new file mode 100644 index 0000000..5b0e83c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.spec.ts @@ -0,0 +1,80 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; +import { AuthServerProvider } from 'app/core/auth/auth-jwt.service'; +import { StateStorageService } from './state-storage.service'; + +describe('Auth JWT', () => { + let service: AuthServerProvider; + let httpMock: HttpTestingController; + let mockStorageService: StateStorageService; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + + mockStorageService = TestBed.inject(StateStorageService); + httpMock = TestBed.inject(HttpTestingController); + service = TestBed.inject(AuthServerProvider); + }); + + describe('Get Token', () => { + it('should return empty token if not found in local storage nor session storage', () => { + const result = service.getToken(); + expect(result).toEqual(''); + }); + + it('should return token from session storage if local storage is empty', () => { + sessionStorage.setItem('jhi-authenticationToken', JSON.stringify('sessionStorageToken')); + const result = service.getToken(); + expect(result).toEqual('sessionStorageToken'); + }); + + it('should return token from localstorage storage', () => { + localStorage.setItem('jhi-authenticationToken', JSON.stringify('localStorageToken')); + const result = service.getToken(); + expect(result).toEqual('localStorageToken'); + }); + }); + + describe('Login', () => { + it('should clear session storage and save in local storage when rememberMe is true', () => { + // GIVEN + mockStorageService.storeAuthenticationToken = jest.fn(); + + // WHEN + service.login({ username: 'John', password: '123', rememberMe: true }).subscribe(); + httpMock.expectOne('api/authenticate').flush({ id_token: '1' }); + + // THEN + httpMock.verify(); + expect(mockStorageService.storeAuthenticationToken).toHaveBeenCalledWith('1', true); + }); + + it('should clear local storage and save in session storage when rememberMe is false', () => { + // GIVEN + mockStorageService.storeAuthenticationToken = jest.fn(); + + // WHEN + service.login({ username: 'John', password: '123', rememberMe: false }).subscribe(); + httpMock.expectOne('api/authenticate').flush({ id_token: '1' }); + + // THEN + httpMock.verify(); + expect(mockStorageService.storeAuthenticationToken).toHaveBeenCalledWith('1', false); + }); + }); + + describe('Logout', () => { + it('should clear storage', () => { + // GIVEN + mockStorageService.clearAuthenticationToken = jest.fn(); + + // WHEN + service.logout().subscribe(); + + // THEN + expect(mockStorageService.clearAuthenticationToken).toHaveBeenCalled(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.ts new file mode 100644 index 0000000..1b6dc1f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/auth-jwt.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { Login } from 'app/login/login.model'; +import { ApplicationConfigService } from '../config/application-config.service'; +import { StateStorageService } from './state-storage.service'; + +type JwtToken = { + id_token: string; +}; + +@Injectable({ providedIn: 'root' }) +export class AuthServerProvider { + constructor( + private http: HttpClient, + private stateStorageService: StateStorageService, + private applicationConfigService: ApplicationConfigService, + ) {} + + getToken(): string { + return this.stateStorageService.getAuthenticationToken() ?? ''; + } + + login(credentials: Login): Observable { + return this.http + .post(this.applicationConfigService.getEndpointFor('api/authenticate'), credentials) + .pipe(map(response => this.authenticateSuccess(response, credentials.rememberMe))); + } + + logout(): Observable { + return new Observable(observer => { + this.stateStorageService.clearAuthenticationToken(); + observer.complete(); + }); + } + + private authenticateSuccess(response: JwtToken, rememberMe: boolean): void { + this.stateStorageService.storeAuthenticationToken(response.id_token, rememberMe); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/state-storage.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/state-storage.service.ts new file mode 100644 index 0000000..14eec1b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/state-storage.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class StateStorageService { + private previousUrlKey = 'previousUrl'; + private authenticationKey = 'jhi-authenticationToken'; + private localeKey = 'locale'; + + storeUrl(url: string): void { + sessionStorage.setItem(this.previousUrlKey, JSON.stringify(url)); + } + + getUrl(): string | null { + const previousUrl = sessionStorage.getItem(this.previousUrlKey); + return previousUrl ? (JSON.parse(previousUrl) as string | null) : previousUrl; + } + + clearUrl(): void { + sessionStorage.removeItem(this.previousUrlKey); + } + + storeAuthenticationToken(authenticationToken: string, rememberMe: boolean): void { + authenticationToken = JSON.stringify(authenticationToken); + this.clearAuthenticationToken(); + if (rememberMe) { + localStorage.setItem(this.authenticationKey, authenticationToken); + } else { + sessionStorage.setItem(this.authenticationKey, authenticationToken); + } + } + + getAuthenticationToken(): string | null { + const authenticationToken = localStorage.getItem(this.authenticationKey) ?? sessionStorage.getItem(this.authenticationKey); + return authenticationToken ? (JSON.parse(authenticationToken) as string | null) : authenticationToken; + } + + clearAuthenticationToken(): void { + sessionStorage.removeItem(this.authenticationKey); + localStorage.removeItem(this.authenticationKey); + } + + storeLocale(locale: string): void { + sessionStorage.setItem(this.localeKey, locale); + } + + getLocale(): string | null { + return sessionStorage.getItem(this.localeKey); + } + + clearLocale(): void { + sessionStorage.removeItem(this.localeKey); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/user-route-access.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/user-route-access.service.ts new file mode 100644 index 0000000..6bdf53b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/auth/user-route-access.service.ts @@ -0,0 +1,33 @@ +import { inject, isDevMode } from '@angular/core'; +import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; +import { map } from 'rxjs/operators'; + +import { AccountService } from 'app/core/auth/account.service'; +import { StateStorageService } from './state-storage.service'; + +export const UserRouteAccessService: CanActivateFn = (next: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + const accountService = inject(AccountService); + const router = inject(Router); + const stateStorageService = inject(StateStorageService); + return accountService.identity().pipe( + map(account => { + if (account) { + const authorities = next.data['authorities']; + + if (!authorities || authorities.length === 0 || accountService.hasAnyAuthority(authorities)) { + return true; + } + + if (isDevMode()) { + console.error('User has not any of required authorities: ', authorities); + } + router.navigate(['accessdenied']); + return false; + } + + stateStorageService.storeUrl(state.url); + router.navigate(['/login']); + return false; + }), + ); +}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.spec.ts new file mode 100644 index 0000000..4451c9b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.spec.ts @@ -0,0 +1,40 @@ +import { TestBed } from '@angular/core/testing'; + +import { ApplicationConfigService } from './application-config.service'; + +describe('ApplicationConfigService', () => { + let service: ApplicationConfigService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ApplicationConfigService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('without prefix', () => { + it('should return correctly', () => { + expect(service.getEndpointFor('api')).toEqual('api'); + }); + + it('should return correctly when passing microservice', () => { + expect(service.getEndpointFor('api', 'microservice')).toEqual('services/microservice/api'); + }); + }); + + describe('with prefix', () => { + beforeEach(() => { + service.setEndpointPrefix('prefix/'); + }); + + it('should return correctly', () => { + expect(service.getEndpointFor('api')).toEqual('prefix/api'); + }); + + it('should return correctly when passing microservice', () => { + expect(service.getEndpointFor('api', 'microservice')).toEqual('prefix/services/microservice/api'); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.ts new file mode 100644 index 0000000..0102e5f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/config/application-config.service.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root', +}) +export class ApplicationConfigService { + private endpointPrefix = ''; + private microfrontend = false; + + setEndpointPrefix(endpointPrefix: string): void { + this.endpointPrefix = endpointPrefix; + } + + setMicrofrontend(microfrontend = true): void { + this.microfrontend = microfrontend; + } + + isMicrofrontend(): boolean { + return this.microfrontend; + } + + getEndpointFor(api: string, microservice?: string): string { + if (microservice) { + return `${this.endpointPrefix}services/${microservice}/${api}`; + } + return `${this.endpointPrefix}${api}`; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth-expired.interceptor.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth-expired.interceptor.ts new file mode 100644 index 0000000..fdca337 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth-expired.interceptor.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { Router } from '@angular/router'; + +import { LoginService } from 'app/login/login.service'; +import { StateStorageService } from 'app/core/auth/state-storage.service'; +import { AccountService } from 'app/core/auth/account.service'; + +@Injectable() +export class AuthExpiredInterceptor implements HttpInterceptor { + constructor( + private loginService: LoginService, + private stateStorageService: StateStorageService, + private router: Router, + private accountService: AccountService, + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + tap({ + error: (err: HttpErrorResponse) => { + if (err.status === 401 && err.url && !err.url.includes('api/account') && this.accountService.isAuthenticated()) { + this.stateStorageService.storeUrl(this.router.routerState.snapshot.url); + this.loginService.logout(); + this.router.navigate(['/login']); + } + }, + }), + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth.interceptor.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth.interceptor.ts new file mode 100644 index 0000000..4a70053 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/auth.interceptor.ts @@ -0,0 +1,31 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { StateStorageService } from 'app/core/auth/state-storage.service'; +import { ApplicationConfigService } from '../config/application-config.service'; + +@Injectable() +export class AuthInterceptor implements HttpInterceptor { + constructor( + private stateStorageService: StateStorageService, + private applicationConfigService: ApplicationConfigService, + ) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + const serverApiUrl = this.applicationConfigService.getEndpointFor(''); + if (!request.url || (request.url.startsWith('http') && !(serverApiUrl && request.url.startsWith(serverApiUrl)))) { + return next.handle(request); + } + + const token: string | null = this.stateStorageService.getAuthenticationToken(); + if (token) { + request = request.clone({ + setHeaders: { + Authorization: `Bearer ${token}`, + }, + }); + } + return next.handle(request); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/error-handler.interceptor.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/error-handler.interceptor.ts new file mode 100644 index 0000000..da8e5eb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/error-handler.interceptor.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; +import { HttpInterceptor, HttpRequest, HttpErrorResponse, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +import { EventManager, EventWithContent } from 'app/core/util/event-manager.service'; + +@Injectable() +export class ErrorHandlerInterceptor implements HttpInterceptor { + constructor(private eventManager: EventManager) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + tap({ + error: (err: HttpErrorResponse) => { + if (!(err.status === 401 && (err.message === '' || err.url?.includes('api/account')))) { + this.eventManager.broadcast(new EventWithContent('appApp.httpError', err)); + } + }, + }), + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/index.ts new file mode 100644 index 0000000..f7e72e3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/index.ts @@ -0,0 +1,29 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; + +import { AuthInterceptor } from 'app/core/interceptor/auth.interceptor'; +import { AuthExpiredInterceptor } from 'app/core/interceptor/auth-expired.interceptor'; +import { ErrorHandlerInterceptor } from 'app/core/interceptor/error-handler.interceptor'; +import { NotificationInterceptor } from 'app/core/interceptor/notification.interceptor'; + +export const httpInterceptorProviders = [ + { + provide: HTTP_INTERCEPTORS, + useClass: AuthInterceptor, + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: AuthExpiredInterceptor, + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: ErrorHandlerInterceptor, + multi: true, + }, + { + provide: HTTP_INTERCEPTORS, + useClass: NotificationInterceptor, + multi: true, + }, +]; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/notification.interceptor.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/notification.interceptor.ts new file mode 100644 index 0000000..6812e24 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/interceptor/notification.interceptor.ts @@ -0,0 +1,38 @@ +import { HttpInterceptor, HttpRequest, HttpResponse, HttpHandler, HttpEvent } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { tap } from 'rxjs/operators'; + +import { AlertService } from 'app/core/util/alert.service'; + +@Injectable() +export class NotificationInterceptor implements HttpInterceptor { + constructor(private alertService: AlertService) {} + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return next.handle(request).pipe( + tap((event: HttpEvent) => { + if (event instanceof HttpResponse) { + let alert: string | null = null; + let alertParams: string | null = null; + + for (const headerKey of event.headers.keys()) { + if (headerKey.toLowerCase().endsWith('app-alert')) { + alert = event.headers.get(headerKey); + } else if (headerKey.toLowerCase().endsWith('app-params')) { + alertParams = decodeURIComponent(event.headers.get(headerKey)!.replace(/\+/g, ' ')); + } + } + + if (alert) { + this.alertService.addAlert({ + type: 'success', + translationKey: alert, + translationParams: { param: alertParams }, + }); + } + } + }), + ); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request-util.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request-util.ts new file mode 100644 index 0000000..694a238 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request-util.ts @@ -0,0 +1,23 @@ +import { HttpParams } from '@angular/common/http'; + +export const createRequestOption = (req?: any): HttpParams => { + let options: HttpParams = new HttpParams(); + + if (req) { + Object.keys(req).forEach(key => { + if (key !== 'sort' && req[key] !== undefined) { + for (const value of [].concat(req[key]).filter(v => v !== '')) { + options = options.append(key, value); + } + } + }); + + if (req.sort) { + req.sort.forEach((val: string) => { + options = options.append('sort', val); + }); + } + } + + return options; +}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request.model.ts new file mode 100644 index 0000000..5de2b69 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/request/request.model.ts @@ -0,0 +1,11 @@ +export interface Pagination { + page: number; + size: number; + sort: string[]; +} + +export interface Search { + query: string; +} + +export interface SearchWithPagination extends Search, Pagination {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.spec.ts new file mode 100644 index 0000000..7244120 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.spec.ts @@ -0,0 +1,285 @@ +import { inject, TestBed } from '@angular/core/testing'; +import { TranslateModule, TranslateService, MissingTranslationHandler } from '@ngx-translate/core'; +import { missingTranslationHandler } from '../../config/translation.config'; + +import { Alert, AlertService } from './alert.service'; + +describe('Alert service test', () => { + describe('Alert Service Test', () => { + let extAlerts: Alert[]; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot({ + missingTranslationHandler: { + provide: MissingTranslationHandler, + useFactory: missingTranslationHandler, + }, + }), + ], + }); + const translateService = TestBed.inject(TranslateService); + translateService.setDefaultLang('en'); + jest.useFakeTimers(); + extAlerts = []; + }); + + it('should produce a proper alert object and fetch it', inject([AlertService], (service: AlertService) => { + expect( + service.addAlert({ + type: 'success', + message: 'Hello Jhipster', + timeout: 3000, + toast: true, + position: 'top left', + }), + ).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + id: 0, + timeout: 3000, + toast: true, + position: 'top left', + } as Alert), + ); + + expect(service.get().length).toBe(1); + expect(service.get()[0]).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + id: 0, + timeout: 3000, + toast: true, + position: 'top left', + } as Alert), + ); + })); + + it('should produce a proper alert object and add it to external alert objects array', inject( + [AlertService], + (service: AlertService) => { + expect( + service.addAlert( + { + type: 'success', + message: 'Hello Jhipster', + timeout: 3000, + toast: true, + position: 'top left', + }, + extAlerts, + ), + ).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + id: 0, + timeout: 3000, + toast: true, + position: 'top left', + } as Alert), + ); + + expect(extAlerts.length).toBe(1); + expect(extAlerts[0]).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + id: 0, + timeout: 3000, + toast: true, + position: 'top left', + } as Alert), + ); + }, + )); + + it('should produce an alert object with correct id', inject([AlertService], (service: AlertService) => { + service.addAlert({ type: 'info', message: 'Hello Jhipster info' }); + expect(service.addAlert({ type: 'success', message: 'Hello Jhipster success' })).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster success', + id: 1, + } as Alert), + ); + + expect(service.get().length).toBe(2); + expect(service.get()[1]).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster success', + id: 1, + } as Alert), + ); + })); + + it('should close an alert correctly', inject([AlertService], (service: AlertService) => { + const alert0 = service.addAlert({ type: 'info', message: 'Hello Jhipster info' }); + const alert1 = service.addAlert({ type: 'info', message: 'Hello Jhipster info 2' }); + const alert2 = service.addAlert({ type: 'success', message: 'Hello Jhipster success' }); + expect(alert2).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster success', + id: 2, + } as Alert), + ); + + expect(service.get().length).toBe(3); + alert1.close?.(service.get()); + expect(service.get().length).toBe(2); + expect(service.get()[1]).not.toEqual( + expect.objectContaining({ + type: 'info', + message: 'Hello Jhipster info 2', + id: 1, + } as Alert), + ); + alert2.close?.(service.get()); + expect(service.get().length).toBe(1); + expect(service.get()[0]).not.toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster success', + id: 2, + } as Alert), + ); + alert0.close?.(service.get()); + expect(service.get().length).toBe(0); + })); + + it('should close an alert on timeout correctly', inject([AlertService], (service: AlertService) => { + service.addAlert({ type: 'info', message: 'Hello Jhipster info' }); + + expect(service.get().length).toBe(1); + + jest.advanceTimersByTime(6000); + + expect(service.get().length).toBe(0); + })); + + it('should clear alerts', inject([AlertService], (service: AlertService) => { + service.addAlert({ type: 'info', message: 'Hello Jhipster info' }); + service.addAlert({ type: 'danger', message: 'Hello Jhipster info' }); + service.addAlert({ type: 'success', message: 'Hello Jhipster info' }); + expect(service.get().length).toBe(3); + service.clear(); + expect(service.get().length).toBe(0); + })); + + it('should produce a scoped alert', inject([AlertService], (service: AlertService) => { + expect( + service.addAlert( + { + type: 'success', + message: 'Hello Jhipster', + timeout: 3000, + toast: true, + position: 'top left', + }, + [], + ), + ).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + id: 0, + timeout: 3000, + toast: true, + position: 'top left', + } as Alert), + ); + + expect(service.get().length).toBe(0); + })); + + it('should produce a success message', inject([AlertService], (service: AlertService) => { + expect(service.addAlert({ type: 'success', message: 'Hello Jhipster' })).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + } as Alert), + ); + })); + + it('should produce a success message with custom position', inject([AlertService], (service: AlertService) => { + expect(service.addAlert({ type: 'success', message: 'Hello Jhipster', position: 'bottom left' })).toEqual( + expect.objectContaining({ + type: 'success', + message: 'Hello Jhipster', + position: 'bottom left', + } as Alert), + ); + })); + + it('should produce a error message', inject([AlertService], (service: AlertService) => { + expect(service.addAlert({ type: 'danger', message: 'Hello Jhipster' })).toEqual( + expect.objectContaining({ + type: 'danger', + message: 'Hello Jhipster', + } as Alert), + ); + })); + + it('should produce a warning message', inject([AlertService], (service: AlertService) => { + expect(service.addAlert({ type: 'warning', message: 'Hello Jhipster' })).toEqual( + expect.objectContaining({ + type: 'warning', + message: 'Hello Jhipster', + } as Alert), + ); + })); + + it('should produce a info message', inject([AlertService], (service: AlertService) => { + expect(service.addAlert({ type: 'info', message: 'Hello Jhipster' })).toEqual( + expect.objectContaining({ + type: 'info', + message: 'Hello Jhipster', + } as Alert), + ); + })); + + it('should produce a info message with translated message if key exists', inject( + [AlertService, TranslateService], + (service: AlertService, translateService: TranslateService) => { + translateService.setTranslation('en', { + 'hello.jhipster': 'Translated message', + }); + expect(service.addAlert({ type: 'info', message: 'Hello Jhipster', translationKey: 'hello.jhipster' })).toEqual( + expect.objectContaining({ + type: 'info', + message: 'Translated message', + } as Alert), + ); + }, + )); + + it('should produce a info message with provided message if key does not exists', inject( + [AlertService, TranslateService], + (service: AlertService) => { + expect(service.addAlert({ type: 'info', message: 'Hello Jhipster', translationKey: 'hello.jhipster' })).toEqual( + expect.objectContaining({ + type: 'info', + message: 'Hello Jhipster', + } as Alert), + ); + }, + )); + + it('should produce a info message with provided key if transltion key does not exist in translations and message is not provided', inject( + [AlertService, TranslateService], + (service: AlertService) => { + expect(service.addAlert({ type: 'info', translationKey: 'hello.jhipster' })).toEqual( + expect.objectContaining({ + type: 'info', + message: 'hello.jhipster', + } as Alert), + ); + }, + )); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.ts new file mode 100644 index 0000000..202eb36 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/alert.service.ts @@ -0,0 +1,93 @@ +import { Injectable, SecurityContext, NgZone } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { TranslateService } from '@ngx-translate/core'; + +import { translationNotFoundMessage } from 'app/config/translation.config'; + +export type AlertType = 'success' | 'danger' | 'warning' | 'info'; + +export interface Alert { + id?: number; + type: AlertType; + message?: string; + translationKey?: string; + translationParams?: { [key: string]: unknown }; + timeout?: number; + toast?: boolean; + position?: string; + close?: (alerts: Alert[]) => void; +} + +@Injectable({ + providedIn: 'root', +}) +export class AlertService { + timeout = 5000; + toast = false; + position = 'top right'; + + // unique id for each alert. Starts from 0. + private alertId = 0; + private alerts: Alert[] = []; + + constructor( + private sanitizer: DomSanitizer, + private ngZone: NgZone, + private translateService: TranslateService, + ) {} + + clear(): void { + this.alerts = []; + } + + get(): Alert[] { + return this.alerts; + } + + /** + * Adds alert to alerts array and returns added alert. + * @param alert Alert to add. If `timeout`, `toast` or `position` is missing then applying default value. + * If `translateKey` is available then it's translation else `message` is used for showing. + * @param extAlerts If missing then adding `alert` to `AlertService` internal array and alerts can be retrieved by `get()`. + * Else adding `alert` to `extAlerts`. + * @returns Added alert + */ + addAlert(alert: Alert, extAlerts?: Alert[]): Alert { + alert.id = this.alertId++; + + if (alert.translationKey) { + const translatedMessage = this.translateService.instant(alert.translationKey, alert.translationParams); + // if translation key exists + if (translatedMessage !== `${translationNotFoundMessage}[${alert.translationKey}]`) { + alert.message = translatedMessage; + } else if (!alert.message) { + alert.message = alert.translationKey; + } + } + + alert.message = this.sanitizer.sanitize(SecurityContext.HTML, alert.message ?? '') ?? ''; + alert.timeout = alert.timeout ?? this.timeout; + alert.toast = alert.toast ?? this.toast; + alert.position = alert.position ?? this.position; + alert.close = (alertsArray: Alert[]) => this.closeAlert(alert.id!, alertsArray); + + (extAlerts ?? this.alerts).push(alert); + + if (alert.timeout > 0) { + setTimeout(() => { + this.closeAlert(alert.id!, extAlerts ?? this.alerts); + }, alert.timeout); + } + + return alert; + } + + private closeAlert(alertId: number, extAlerts?: Alert[]): void { + const alerts = extAlerts ?? this.alerts; + const alertIndex = alerts.map(alert => alert.id).indexOf(alertId); + // if found alert then remove + if (alertIndex >= 0) { + alerts.splice(alertIndex, 1); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.spec.ts new file mode 100644 index 0000000..fccbcc6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.spec.ts @@ -0,0 +1,34 @@ +import { TestBed } from '@angular/core/testing'; + +import { DataUtils } from './data-util.service'; + +describe('Data Utils Service Test', () => { + let service: DataUtils; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [DataUtils], + }); + service = TestBed.inject(DataUtils); + }); + + describe('byteSize', () => { + it('should return the bytesize of the text', () => { + expect(service.byteSize('Hello JHipster')).toBe(`10.5 bytes`); + }); + }); + + describe('openFile', () => { + it('should open the file in the new window', () => { + const newWindow = { ...window }; + newWindow.document.write = jest.fn(); + window.open = jest.fn(() => newWindow); + window.URL.createObjectURL = jest.fn(); + // 'JHipster' in base64 is 'SkhpcHN0ZXI=' + const data = 'SkhpcHN0ZXI='; + const contentType = 'text/plain'; + service.openFile(data, contentType); + expect(window.open).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.ts new file mode 100644 index 0000000..8ac137e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/data-util.service.ts @@ -0,0 +1,130 @@ +import { Buffer } from 'buffer'; +import { Injectable } from '@angular/core'; +import { FormGroup } from '@angular/forms'; +import { Observable, Observer } from 'rxjs'; + +export type FileLoadErrorType = 'not.image' | 'could.not.extract'; + +export interface FileLoadError { + message: string; + key: FileLoadErrorType; + params?: any; +} + +/** + * An utility service for data. + */ +@Injectable({ + providedIn: 'root', +}) +export class DataUtils { + /** + * Method to find the byte size of the string provides + */ + byteSize(base64String: string): string { + return this.formatAsBytes(this.size(base64String)); + } + + /** + * Method to open file + */ + openFile(data: string, contentType: string | null | undefined): void { + contentType = contentType ?? ''; + + const byteCharacters = Buffer.from(data, 'base64').toString('binary'); + const byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) { + byteNumbers[i] = byteCharacters.charCodeAt(i); + } + const byteArray = new Uint8Array(byteNumbers); + const blob = new Blob([byteArray], { + type: contentType, + }); + const fileURL = window.URL.createObjectURL(blob); + const win = window.open(fileURL); + win!.onload = function () { + URL.revokeObjectURL(fileURL); + }; + } + + /** + * Sets the base 64 data & file type of the 1st file on the event (event.target.files[0]) in the passed entity object + * and returns an observable. + * + * @param event the object containing the file (at event.target.files[0]) + * @param editForm the form group where the input field is located + * @param field the field name to set the file's 'base 64 data' on + * @param isImage boolean representing if the file represented by the event is an image + * @returns an observable that loads file to form field and completes if sussessful + * or returns error as FileLoadError on failure + */ + loadFileToForm(event: Event, editForm: FormGroup, field: string, isImage: boolean): Observable { + return new Observable((observer: Observer) => { + const eventTarget: HTMLInputElement | null = event.target as HTMLInputElement | null; + if (eventTarget?.files?.[0]) { + const file: File = eventTarget.files[0]; + if (isImage && !file.type.startsWith('image/')) { + const error: FileLoadError = { + message: `File was expected to be an image but was found to be '${file.type}'`, + key: 'not.image', + params: { fileType: file.type }, + }; + observer.error(error); + } else { + const fieldContentType: string = field + 'ContentType'; + this.toBase64(file, (base64Data: string) => { + editForm.patchValue({ + [field]: base64Data, + [fieldContentType]: file.type, + }); + observer.next(); + observer.complete(); + }); + } + } else { + const error: FileLoadError = { + message: 'Could not extract file', + key: 'could.not.extract', + params: { event }, + }; + observer.error(error); + } + }); + } + + /** + * Method to convert the file to base64 + */ + private toBase64(file: File, callback: (base64Data: string) => void): void { + const fileReader: FileReader = new FileReader(); + fileReader.onload = (e: ProgressEvent) => { + if (typeof e.target?.result === 'string') { + const base64Data: string = e.target.result.substring(e.target.result.indexOf('base64,') + 'base64,'.length); + callback(base64Data); + } + }; + fileReader.readAsDataURL(file); + } + + private endsWith(suffix: string, str: string): boolean { + return str.includes(suffix, str.length - suffix.length); + } + + private paddingSize(value: string): number { + if (this.endsWith('==', value)) { + return 2; + } + if (this.endsWith('=', value)) { + return 1; + } + return 0; + } + + private size(value: string): number { + return (value.length / 4) * 3 - this.paddingSize(value); + } + + private formatAsBytes(size: number): string { + return size.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' bytes'; // NOSONAR + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.spec.ts new file mode 100644 index 0000000..48d4e19 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.spec.ts @@ -0,0 +1,84 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { EventManager, EventWithContent } from './event-manager.service'; + +describe('Event Manager tests', () => { + describe('EventWithContent', () => { + it('should create correctly EventWithContent', () => { + // WHEN + const eventWithContent = new EventWithContent('name', 'content'); + + // THEN + expect(eventWithContent).toEqual({ name: 'name', content: 'content' }); + }); + }); + + describe('EventManager', () => { + let recievedEvent: EventWithContent | string | null; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [EventManager], + }); + recievedEvent = null; + }); + + it('should not fail when nosubscriber and broadcasting', inject([EventManager], (eventManager: EventManager) => { + expect(eventManager.observer).toBeUndefined(); + eventManager.broadcast({ name: 'modifier', content: 'modified something' }); + })); + + it('should create an observable and callback when broadcasted EventWithContent', inject( + [EventManager], + (eventManager: EventManager) => { + // GIVEN + eventManager.subscribe('modifier', (event: EventWithContent | string) => (recievedEvent = event)); + + // WHEN + eventManager.broadcast({ name: 'unrelatedModifier', content: 'unrelated modification' }); + // THEN + expect(recievedEvent).toBeNull(); + + // WHEN + eventManager.broadcast({ name: 'modifier', content: 'modified something' }); + // THEN + expect(recievedEvent).toEqual({ name: 'modifier', content: 'modified something' }); + }, + )); + + it('should create an observable and callback when broadcasted string', inject([EventManager], (eventManager: EventManager) => { + // GIVEN + eventManager.subscribe('modifier', (event: EventWithContent | string) => (recievedEvent = event)); + + // WHEN + eventManager.broadcast('unrelatedModifier'); + // THEN + expect(recievedEvent).toBeNull(); + + // WHEN + eventManager.broadcast('modifier'); + // THEN + expect(recievedEvent).toEqual('modifier'); + })); + + it('should subscribe to multiple events', inject([EventManager], (eventManager: EventManager) => { + // GIVEN + eventManager.subscribe(['modifier', 'modifier2'], (event: EventWithContent | string) => (recievedEvent = event)); + + // WHEN + eventManager.broadcast('unrelatedModifier'); + // THEN + expect(recievedEvent).toBeNull(); + + // WHEN + eventManager.broadcast({ name: 'modifier', content: 'modified something' }); + // THEN + expect(recievedEvent).toEqual({ name: 'modifier', content: 'modified something' }); + + // WHEN + eventManager.broadcast('modifier2'); + // THEN + expect(recievedEvent).toEqual('modifier2'); + })); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.ts new file mode 100644 index 0000000..a73d212 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/event-manager.service.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@angular/core'; +import { Observable, Observer, Subscription } from 'rxjs'; +import { filter, share } from 'rxjs/operators'; + +export class EventWithContent { + constructor( + public name: string, + public content: T, + ) {} +} + +/** + * An utility class to manage RX events + */ +@Injectable({ + providedIn: 'root', +}) +export class EventManager { + observable: Observable | string>; + observer?: Observer | string>; + + constructor() { + this.observable = new Observable((observer: Observer | string>) => { + this.observer = observer; + }).pipe(share()); + } + + /** + * Method to broadcast the event to observer + */ + broadcast(event: EventWithContent | string): void { + if (this.observer) { + this.observer.next(event); + } + } + + /** + * Method to subscribe to an event with callback + * @param eventNames Single event name or array of event names to what subscribe + * @param callback Callback to run when the event occurs + */ + subscribe(eventNames: string | string[], callback: (event: EventWithContent | string) => void): Subscription { + if (typeof eventNames === 'string') { + eventNames = [eventNames]; + } + return this.observable + .pipe( + filter((event: EventWithContent | string) => { + for (const eventName of eventNames) { + if ((typeof event === 'string' && event === eventName) || (typeof event !== 'string' && event.name === eventName)) { + return true; + } + } + return false; + }), + ) + .subscribe(callback); + } + + /** + * Method to unsubscribe the subscription + */ + destroy(subscriber: Subscription): void { + subscriber.unsubscribe(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.spec.ts new file mode 100644 index 0000000..429647c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.spec.ts @@ -0,0 +1,18 @@ +import { filterNaN, isPresent } from './operators'; + +describe('Operators Test', () => { + describe('isPresent', () => { + it('should remove null and undefined values', () => { + expect([1, null, undefined].filter(isPresent)).toEqual([1]); + }); + }); + + describe('filterNaN', () => { + it('should return 0 for NaN', () => { + expect(filterNaN(NaN)).toBe(0); + }); + it('should return number for a number', () => { + expect(filterNaN(12345)).toBe(12345); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.ts new file mode 100644 index 0000000..c224592 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/operators.ts @@ -0,0 +1,9 @@ +/* + * Function used to workaround https://github.com/microsoft/TypeScript/issues/16069 + * es2019 alternative `const filteredArr = myArr.flatMap((x) => x ? x : []);` + */ +export function isPresent(t: T | undefined | null | void): t is T { + return t !== undefined && t !== null; +} + +export const filterNaN = (input: number): number => (isNaN(input) ? 0 : input); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.spec.ts new file mode 100644 index 0000000..40b6c75 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.spec.ts @@ -0,0 +1,36 @@ +import { inject, TestBed } from '@angular/core/testing'; + +import { ParseLinks } from './parse-links.service'; + +describe('Parse links service test', () => { + describe('Parse Links Service Test', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ParseLinks], + }); + }); + + it('should throw an error when passed an empty string', inject([ParseLinks], (service: ParseLinks) => { + expect(function () { + service.parse(''); + }).toThrow(new Error('input must not be of zero length')); + })); + + it('should throw an error when passed without comma', inject([ParseLinks], (service: ParseLinks) => { + expect(function () { + service.parse('test'); + }).toThrow(new Error('section could not be split on ";"')); + })); + + it('should throw an error when passed without semicolon', inject([ParseLinks], (service: ParseLinks) => { + expect(function () { + service.parse('test,test2'); + }).toThrow(new Error('section could not be split on ";"')); + })); + + it('should return links when headers are passed', inject([ParseLinks], (service: ParseLinks) => { + const links = { last: 0, first: 0 }; + expect(service.parse(' ; rel="last",; rel="first"')).toEqual(links); + })); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.ts new file mode 100644 index 0000000..dc1eb0e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/core/util/parse-links.service.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; + +/** + * An utility service for link parsing. + */ +@Injectable({ + providedIn: 'root', +}) +export class ParseLinks { + /** + * Method to parse the links + */ + parse(header: string): { [key: string]: number } { + if (header.length === 0) { + throw new Error('input must not be of zero length'); + } + + // Split parts by comma + const parts: string[] = header.split(','); + const links: { [key: string]: number } = {}; + + // Parse each part into a named link + parts.forEach(p => { + const section: string[] = p.split(';'); + + if (section.length !== 2) { + throw new Error('section could not be split on ";"'); + } + + const url: string = section[0].replace(/<(.*)>/, '$1').trim(); // NOSONAR + const queryString: { [key: string]: string | undefined } = {}; + + url.replace(/([^?=&]+)(=([^&]*))?/g, (_$0: string, $1: string | undefined, _$2: string | undefined, $3: string | undefined) => { + if ($1 !== undefined) { + queryString[$1] = $3; + } + return $3 ?? ''; + }); + + if (queryString.page !== undefined) { + const name: string = section[1].replace(/rel="(.*)"/, '$1').trim(); + links[name] = parseInt(queryString.page, 10); + } + }); + return links; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-navbar-items.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-navbar-items.ts new file mode 100644 index 0000000..9f96a68 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-navbar-items.ts @@ -0,0 +1,3 @@ +import NavbarItem from 'app/layouts/navbar/navbar-item.model'; + +export const EntityNavbarItems: NavbarItem[] = []; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-routing.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-routing.module.ts new file mode 100644 index 0000000..fe1354d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/entity-routing.module.ts @@ -0,0 +1,11 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +@NgModule({ + imports: [ + RouterModule.forChild([ + /* jhipster-needle-add-entity-route - JHipster will add entity modules routes here */ + ]), + ], +}) +export class EntityRoutingModule {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.model.ts new file mode 100644 index 0000000..74137a9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.model.ts @@ -0,0 +1,15 @@ +export interface IUser { + id: number; + login?: string; +} + +export class User implements IUser { + constructor( + public id: number, + public login: string, + ) {} +} + +export function getUserIdentifier(user: IUser): number { + return user.id; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.spec.ts new file mode 100644 index 0000000..77ac75f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.spec.ts @@ -0,0 +1,109 @@ +import { TestBed } from '@angular/core/testing'; +import { HttpErrorResponse } from '@angular/common/http'; +import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; + +import { User, IUser } from './user.model'; + +import { UserService } from './user.service'; + +describe('User Service', () => { + let service: UserService; + let httpMock: HttpTestingController; + let expectedResult: IUser | IUser[] | boolean | number | null; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule], + }); + expectedResult = null; + service = TestBed.inject(UserService); + httpMock = TestBed.inject(HttpTestingController); + }); + + afterEach(() => { + httpMock.verify(); + }); + + describe('Service methods', () => { + it('should return Users', () => { + service.query().subscribe(received => { + expectedResult = received.body; + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush([new User(123, 'user')]); + expect(expectedResult).toEqual([{ id: 123, login: 'user' }]); + }); + + it('should propagate not found response', () => { + service.query().subscribe({ + error: (error: HttpErrorResponse) => (expectedResult = error.status), + }); + + const req = httpMock.expectOne({ method: 'GET' }); + req.flush('Internal Server Error', { + status: 500, + statusText: 'Internal Server Error', + }); + expect(expectedResult).toEqual(500); + }); + + describe('addUserToCollectionIfMissing', () => { + it('should add a User to an empty array', () => { + const user: IUser = { id: 123 }; + expectedResult = service.addUserToCollectionIfMissing([], user); + expect(expectedResult).toHaveLength(1); + expect(expectedResult).toContain(user); + }); + + it('should not add a User to an array that contains it', () => { + const user: IUser = { id: 123 }; + const userCollection: IUser[] = [ + { + ...user, + }, + { id: 456 }, + ]; + expectedResult = service.addUserToCollectionIfMissing(userCollection, user); + expect(expectedResult).toHaveLength(2); + }); + + it("should add a User to an array that doesn't contain it", () => { + const user: IUser = { id: 123 }; + const userCollection: IUser[] = [{ id: 456 }]; + expectedResult = service.addUserToCollectionIfMissing(userCollection, user); + expect(expectedResult).toHaveLength(2); + expect(expectedResult).toContain(user); + }); + + it('should add only unique User to an array', () => { + const userArray: IUser[] = [{ id: 123 }, { id: 456 }, { id: 5034 }]; + const userCollection: IUser[] = [{ id: 456 }]; + expectedResult = service.addUserToCollectionIfMissing(userCollection, ...userArray); + expect(expectedResult).toHaveLength(3); + }); + + it('should accept varargs', () => { + const user: IUser = { id: 123 }; + const user2: IUser = { id: 456 }; + expectedResult = service.addUserToCollectionIfMissing([], user, user2); + expect(expectedResult).toHaveLength(2); + expect(expectedResult).toContain(user); + expect(expectedResult).toContain(user2); + }); + + it('should accept null and undefined values', () => { + const user: IUser = { id: 123 }; + expectedResult = service.addUserToCollectionIfMissing([], null, user, undefined); + expect(expectedResult).toHaveLength(1); + expect(expectedResult).toContain(user); + }); + + it('should return initial array if no users is added', () => { + const userCollection: IUser[] = [{ id: 456 }]; + expectedResult = service.addUserToCollectionIfMissing(userCollection, null, undefined); + expect(expectedResult).toEqual(userCollection); + }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.ts new file mode 100644 index 0000000..02f2c65 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/entities/user/user.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { createRequestOption } from 'app/core/request/request-util'; +import { isPresent } from 'app/core/util/operators'; +import { Pagination } from 'app/core/request/request.model'; +import { IUser, getUserIdentifier } from './user.model'; + +@Injectable({ providedIn: 'root' }) +export class UserService { + private resourceUrl = this.applicationConfigService.getEndpointFor('api/users'); + + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + query(req?: Pagination): Observable> { + const options = createRequestOption(req); + return this.http.get(this.resourceUrl, { params: options, observe: 'response' }); + } + + compareUser(o1: Pick | null, o2: Pick | null): boolean { + return o1 && o2 ? o1.id === o2.id : o1 === o2; + } + + addUserToCollectionIfMissing & Pick>( + userCollection: Type[], + ...usersToCheck: (Type | null | undefined)[] + ): IUser[] { + const users: Type[] = usersToCheck.filter(isPresent); + if (users.length > 0) { + const userCollectionIdentifiers = userCollection.map(userItem => getUserIdentifier(userItem)!); + const usersToAdd = users.filter(userItem => { + const userIdentifier = getUserIdentifier(userItem); + if (userCollectionIdentifiers.includes(userIdentifier)) { + return false; + } + userCollectionIdentifiers.push(userIdentifier); + return true; + }); + return [...usersToAdd, ...userCollection]; + } + return userCollection; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.html new file mode 100644 index 0000000..b21f3f9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.html @@ -0,0 +1,78 @@ +
+
+ +
+ +
+

Welcome, Java Hipster! (App)

+ +

This is your homepage

+ +
+
+ You are logged in as user "{{ account.login }}". +
+ +
+ If you want to + sign in, you can try the default accounts:
- Administrator (login="admin" and password="admin")
- User (login="user" and + password="user").
+
+ +
+ You don't have an account yet?  + Register a new account +
+
+ +

If you have any question on JHipster:

+ + + +

+ If you like JHipster, don't forget to give us a star on + GitHub! +

+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.scss new file mode 100644 index 0000000..a22e3e8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.scss @@ -0,0 +1,23 @@ +/* ========================================================================== +Main page styles +========================================================================== */ + +.hipster { + display: inline-block; + width: 347px; + height: 497px; + background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; + background-size: contain; +} + +/* wait autoprefixer update to allow simple generation of high pixel density media query */ +@media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (-moz-min-device-pixel-ratio: 2), + only screen and (-o-min-device-pixel-ratio: 2/1), + only screen and (min-resolution: 192dpi), + only screen and (min-resolution: 2dppx) { + .hipster { + background: url('../../content/images/jhipster_family_member_1.svg') no-repeat center top; + background-size: contain; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.spec.ts new file mode 100644 index 0000000..c512223 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.spec.ts @@ -0,0 +1,111 @@ +jest.mock('app/core/auth/account.service'); + +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of, Subject } from 'rxjs'; + +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; + +import HomeComponent from './home.component'; + +describe('Home Component', () => { + let comp: HomeComponent; + let fixture: ComponentFixture; + let mockAccountService: AccountService; + let mockRouter: Router; + const account: Account = { + activated: true, + authorities: [], + email: '', + firstName: null, + langKey: '', + lastName: null, + login: 'login', + imageUrl: null, + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HomeComponent, RouterTestingModule.withRoutes([])], + providers: [AccountService], + }) + .overrideTemplate(HomeComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(HomeComponent); + comp = fixture.componentInstance; + mockAccountService = TestBed.inject(AccountService); + mockAccountService.identity = jest.fn(() => of(null)); + mockAccountService.getAuthenticationState = jest.fn(() => of(null)); + + mockRouter = TestBed.inject(Router); + jest.spyOn(mockRouter, 'navigate').mockImplementation(() => Promise.resolve(true)); + }); + + describe('ngOnInit', () => { + it('Should synchronize account variable with current account', () => { + // GIVEN + const authenticationState = new Subject(); + mockAccountService.getAuthenticationState = jest.fn(() => authenticationState.asObservable()); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.account).toBeNull(); + + // WHEN + authenticationState.next(account); + + // THEN + expect(comp.account).toEqual(account); + + // WHEN + authenticationState.next(null); + + // THEN + expect(comp.account).toBeNull(); + }); + }); + + describe('login', () => { + it('Should navigate to /login on login', () => { + // WHEN + comp.login(); + + // THEN + expect(mockRouter.navigate).toHaveBeenCalledWith(['/login']); + }); + }); + + describe('ngOnDestroy', () => { + it('Should destroy authentication state subscription on component destroy', () => { + // GIVEN + const authenticationState = new Subject(); + mockAccountService.getAuthenticationState = jest.fn(() => authenticationState.asObservable()); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.account).toBeNull(); + + // WHEN + authenticationState.next(account); + + // THEN + expect(comp.account).toEqual(account); + + // WHEN + comp.ngOnDestroy(); + authenticationState.next(null); + + // THEN + expect(comp.account).toEqual(account); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.ts new file mode 100644 index 0000000..99c64b2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/home/home.component.ts @@ -0,0 +1,42 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Router, RouterModule } from '@angular/router'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import SharedModule from 'app/shared/shared.module'; +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; + +@Component({ + standalone: true, + selector: 'jhi-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'], + imports: [SharedModule, RouterModule], +}) +export default class HomeComponent implements OnInit, OnDestroy { + account: Account | null = null; + + private readonly destroy$ = new Subject(); + + constructor( + private accountService: AccountService, + private router: Router, + ) {} + + ngOnInit(): void { + this.accountService + .getAuthenticationState() + .pipe(takeUntil(this.destroy$)) + .subscribe(account => (this.account = account)); + } + + login(): void { + this.router.navigate(['/login']); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.html new file mode 100644 index 0000000..c1b52a5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.html @@ -0,0 +1,15 @@ +
+
+
+ +
+ +
+

Error page!

+ +
+
{{ errorMessage }}
+
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.ts new file mode 100644 index 0000000..9767ef6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.component.ts @@ -0,0 +1,47 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { TranslateService } from '@ngx-translate/core'; +import SharedModule from 'app/shared/shared.module'; + +@Component({ + standalone: true, + selector: 'jhi-error', + templateUrl: './error.component.html', + imports: [SharedModule], +}) +export default class ErrorComponent implements OnInit, OnDestroy { + errorMessage?: string; + errorKey?: string; + langChangeSubscription?: Subscription; + + constructor( + private translateService: TranslateService, + private route: ActivatedRoute, + ) {} + + ngOnInit(): void { + this.route.data.subscribe(routeData => { + if (routeData.errorMessage) { + this.errorKey = routeData.errorMessage; + this.getErrorMessageTranslation(); + this.langChangeSubscription = this.translateService.onLangChange.subscribe(() => this.getErrorMessageTranslation()); + } + }); + } + + ngOnDestroy(): void { + if (this.langChangeSubscription) { + this.langChangeSubscription.unsubscribe(); + } + } + + private getErrorMessageTranslation(): void { + this.errorMessage = ''; + if (this.errorKey) { + this.translateService.get(this.errorKey).subscribe(translatedErrorMessage => { + this.errorMessage = translatedErrorMessage; + }); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.route.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.route.ts new file mode 100644 index 0000000..85f911b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/error/error.route.ts @@ -0,0 +1,31 @@ +import { Routes } from '@angular/router'; + +import ErrorComponent from './error.component'; + +export const errorRoute: Routes = [ + { + path: 'error', + component: ErrorComponent, + title: 'error.title', + }, + { + path: 'accessdenied', + component: ErrorComponent, + data: { + errorMessage: 'error.http.403', + }, + title: 'error.title', + }, + { + path: '404', + component: ErrorComponent, + data: { + errorMessage: 'error.http.404', + }, + title: 'error.title', + }, + { + path: '**', + redirectTo: '/404', + }, +]; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.html new file mode 100644 index 0000000..30aca31 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.html @@ -0,0 +1,3 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.ts new file mode 100644 index 0000000..7ab0938 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/footer/footer.component.ts @@ -0,0 +1,8 @@ +import { Component } from '@angular/core'; + +@Component({ + standalone: true, + selector: 'jhi-footer', + templateUrl: './footer.component.html', +}) +export default class FooterComponent {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.html new file mode 100644 index 0000000..3ac9be9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.html @@ -0,0 +1,13 @@ + + +
+ +
+ +
+
+ +
+ + +
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.spec.ts new file mode 100644 index 0000000..488b91c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.spec.ts @@ -0,0 +1,230 @@ +jest.mock('app/core/auth/account.service'); + +import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; +import { Router, TitleStrategy } from '@angular/router'; +import { Title } from '@angular/platform-browser'; +import { RouterTestingModule } from '@angular/router/testing'; +import { DOCUMENT } from '@angular/common'; +import { Component } from '@angular/core'; +import { of } from 'rxjs'; +import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core'; + +import { AccountService } from 'app/core/auth/account.service'; + +import { AppPageTitleStrategy } from 'app/app-page-title-strategy'; +import MainComponent from './main.component'; + +describe('MainComponent', () => { + let comp: MainComponent; + let fixture: ComponentFixture; + let titleService: Title; + let translateService: TranslateService; + let mockAccountService: AccountService; + const routerState: any = { snapshot: { root: { data: {} } } }; + let router: Router; + let document: Document; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), RouterTestingModule], + declarations: [MainComponent], + providers: [Title, AccountService, { provide: TitleStrategy, useClass: AppPageTitleStrategy }], + }) + .overrideTemplate(MainComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(MainComponent); + comp = fixture.componentInstance; + titleService = TestBed.inject(Title); + translateService = TestBed.inject(TranslateService); + mockAccountService = TestBed.inject(AccountService); + mockAccountService.identity = jest.fn(() => of(null)); + mockAccountService.getAuthenticationState = jest.fn(() => of(null)); + router = TestBed.inject(Router); + document = TestBed.inject(DOCUMENT); + }); + + describe('page title', () => { + const defaultPageTitle = 'global.title'; + const parentRoutePageTitle = 'parentTitle'; + const childRoutePageTitle = 'childTitle'; + const langChangeEvent: LangChangeEvent = { lang: 'en', translations: null }; + + beforeEach(() => { + routerState.snapshot.root = { data: {} }; + jest.spyOn(translateService, 'get').mockImplementation((key: string | string[]) => of(`${key as string} translated`)); + translateService.currentLang = 'en'; + jest.spyOn(titleService, 'setTitle'); + comp.ngOnInit(); + }); + + describe('navigation end', () => { + it('should set page title to default title if pageTitle is missing on routes', fakeAsync(() => { + // WHEN + router.navigateByUrl(''); + tick(); + + // THEN + expect(document.title).toBe(defaultPageTitle + ' translated'); + })); + + it('should set page title to root route pageTitle if there is no child routes', fakeAsync(() => { + // GIVEN + router.resetConfig([{ path: '', title: parentRoutePageTitle, component: BlankComponent }]); + + // WHEN + router.navigateByUrl(''); + tick(); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + })); + + it('should set page title to child route pageTitle if child routes exist and pageTitle is set for child route', fakeAsync(() => { + // GIVEN + router.resetConfig([ + { + path: 'home', + title: parentRoutePageTitle, + children: [{ path: '', title: childRoutePageTitle, component: BlankComponent }], + }, + ]); + + // WHEN + router.navigateByUrl('home'); + tick(); + + // THEN + expect(document.title).toBe(childRoutePageTitle + ' translated'); + })); + + it('should set page title to parent route pageTitle if child routes exists but pageTitle is not set for child route data', fakeAsync(() => { + // GIVEN + router.resetConfig([ + { + path: 'home', + title: parentRoutePageTitle, + children: [{ path: '', component: BlankComponent }], + }, + ]); + + // WHEN + router.navigateByUrl('home'); + tick(); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + })); + }); + + describe('language change', () => { + it('should set page title to default title if pageTitle is missing on routes', () => { + // WHEN + translateService.onLangChange.emit(langChangeEvent); + + // THEN + expect(document.title).toBe(defaultPageTitle + ' translated'); + }); + + it('should set page title to root route pageTitle if there is no child routes', fakeAsync(() => { + // GIVEN + routerState.snapshot.root.data = { pageTitle: parentRoutePageTitle }; + router.resetConfig([{ path: '', title: parentRoutePageTitle, component: BlankComponent }]); + + // WHEN + router.navigateByUrl(''); + tick(); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + + // GIVEN + document.title = 'other title'; + + // WHEN + translateService.onLangChange.emit(langChangeEvent); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + })); + + it('should set page title to child route pageTitle if child routes exist and pageTitle is set for child route', fakeAsync(() => { + // GIVEN + router.resetConfig([ + { + path: 'home', + title: parentRoutePageTitle, + children: [{ path: '', title: childRoutePageTitle, component: BlankComponent }], + }, + ]); + + // WHEN + router.navigateByUrl('home'); + tick(); + + // THEN + expect(document.title).toBe(childRoutePageTitle + ' translated'); + + // GIVEN + document.title = 'other title'; + + // WHEN + translateService.onLangChange.emit(langChangeEvent); + + // THEN + expect(document.title).toBe(childRoutePageTitle + ' translated'); + })); + + it('should set page title to parent route pageTitle if child routes exists but pageTitle is not set for child route data', fakeAsync(() => { + // GIVEN + router.resetConfig([ + { + path: 'home', + title: parentRoutePageTitle, + children: [{ path: '', component: BlankComponent }], + }, + ]); + + // WHEN + router.navigateByUrl('home'); + tick(); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + + // GIVEN + document.title = 'other title'; + + // WHEN + translateService.onLangChange.emit(langChangeEvent); + + // THEN + expect(document.title).toBe(parentRoutePageTitle + ' translated'); + })); + }); + }); + + describe('page language attribute', () => { + it('should change page language attribute on language change', () => { + // GIVEN + comp.ngOnInit(); + + // WHEN + translateService.onLangChange.emit({ lang: 'lang1', translations: null }); + + // THEN + expect(document.querySelector('html')?.getAttribute('lang')).toEqual('lang1'); + + // WHEN + translateService.onLangChange.emit({ lang: 'lang2', translations: null }); + + // THEN + expect(document.querySelector('html')?.getAttribute('lang')).toEqual('lang2'); + }); + }); +}); + +@Component({ template: '' }) +export class BlankComponent {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.ts new file mode 100644 index 0000000..e70bd5a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.component.ts @@ -0,0 +1,37 @@ +import { Component, OnInit, RendererFactory2, Renderer2 } from '@angular/core'; +import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; +import dayjs from 'dayjs/esm'; + +import { AccountService } from 'app/core/auth/account.service'; +import { AppPageTitleStrategy } from 'app/app-page-title-strategy'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'jhi-main', + templateUrl: './main.component.html', + providers: [AppPageTitleStrategy], +}) +export default class MainComponent implements OnInit { + private renderer: Renderer2; + + constructor( + private router: Router, + private appPageTitleStrategy: AppPageTitleStrategy, + private accountService: AccountService, + private translateService: TranslateService, + rootRenderer: RendererFactory2, + ) { + this.renderer = rootRenderer.createRenderer(document.querySelector('html'), null); + } + + ngOnInit(): void { + // try to log in automatically + this.accountService.identity().subscribe(); + + this.translateService.onLangChange.subscribe((langChangeEvent: LangChangeEvent) => { + this.appPageTitleStrategy.updateTitle(this.router.routerState.snapshot); + dayjs.locale(langChangeEvent.lang); + this.renderer.setAttribute(document.querySelector('html'), 'lang', langChangeEvent.lang); + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.module.ts new file mode 100644 index 0000000..f033549 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/main/main.module.ts @@ -0,0 +1,13 @@ +import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; + +import SharedModule from 'app/shared/shared.module'; +import FooterComponent from '../footer/footer.component'; +import PageRibbonComponent from '../profiles/page-ribbon.component'; +import MainComponent from './main.component'; + +@NgModule({ + imports: [SharedModule, RouterModule, FooterComponent, PageRibbonComponent], + declarations: [MainComponent], +}) +export default class MainModule {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/active-menu.directive.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/active-menu.directive.ts new file mode 100644 index 0000000..3b0e48d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/active-menu.directive.ts @@ -0,0 +1,32 @@ +import { Directive, OnInit, ElementRef, Renderer2, Input } from '@angular/core'; +import { TranslateService, LangChangeEvent } from '@ngx-translate/core'; + +@Directive({ + standalone: true, + selector: '[jhiActiveMenu]', +}) +export default class ActiveMenuDirective implements OnInit { + @Input() jhiActiveMenu?: string; + + constructor( + private el: ElementRef, + private renderer: Renderer2, + private translateService: TranslateService, + ) {} + + ngOnInit(): void { + this.translateService.onLangChange.subscribe((event: LangChangeEvent) => { + this.updateActiveFlag(event.lang); + }); + + this.updateActiveFlag(this.translateService.currentLang); + } + + updateActiveFlag(selectedLanguage: string): void { + if (this.jhiActiveMenu === selectedLanguage) { + this.renderer.addClass(this.el.nativeElement, 'active'); + } else { + this.renderer.removeClass(this.el.nativeElement, 'active'); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar-item.model.d.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar-item.model.d.ts new file mode 100644 index 0000000..d6f1cca --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar-item.model.d.ts @@ -0,0 +1,7 @@ +type NavbarItem = { + name: string; + route: string; + translationKey: string; +}; + +export default NavbarItem; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.html new file mode 100644 index 0000000..8bff065 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.html @@ -0,0 +1,192 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.scss new file mode 100644 index 0000000..4c038a2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.scss @@ -0,0 +1,36 @@ +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; + +/* ========================================================================== +Navbar +========================================================================== */ + +.navbar-version { + font-size: 0.65em; + color: $navbar-dark-color; +} + +.profile-image { + height: 1.75em; + width: 1.75em; +} + +.navbar { + padding: 0.2rem 1rem; + + a.nav-link { + font-weight: 400; + } +} + +/* ========================================================================== +Logo styles +========================================================================== */ +.logo-img { + height: 45px; + width: 45px; + display: inline-block; + vertical-align: middle; + background: url('/content/images/logo-jhipster.png') no-repeat center center; + background-size: contain; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.spec.ts new file mode 100644 index 0000000..983b1c5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.spec.ts @@ -0,0 +1,96 @@ +jest.mock('app/login/login.service'); + +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of } from 'rxjs'; +import { TranslateModule } from '@ngx-translate/core'; + +import { ProfileInfo } from 'app/layouts/profiles/profile-info.model'; +import { Account } from 'app/core/auth/account.model'; +import { AccountService } from 'app/core/auth/account.service'; +import { ProfileService } from 'app/layouts/profiles/profile.service'; +import { LoginService } from 'app/login/login.service'; + +import NavbarComponent from './navbar.component'; + +describe('Navbar Component', () => { + let comp: NavbarComponent; + let fixture: ComponentFixture; + let accountService: AccountService; + let profileService: ProfileService; + const account: Account = { + activated: true, + authorities: [], + email: '', + firstName: 'John', + langKey: '', + lastName: 'Doe', + login: 'john.doe', + imageUrl: '', + }; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [NavbarComponent, HttpClientTestingModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot()], + providers: [LoginService], + }) + .overrideTemplate(NavbarComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarComponent); + comp = fixture.componentInstance; + accountService = TestBed.inject(AccountService); + profileService = TestBed.inject(ProfileService); + }); + + it('Should call profileService.getProfileInfo on init', () => { + // GIVEN + jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(new ProfileInfo())); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(profileService.getProfileInfo).toHaveBeenCalled(); + }); + + it('Should hold current authenticated user in variable account', () => { + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.account).toBeNull(); + + // WHEN + accountService.authenticate(account); + + // THEN + expect(comp.account).toEqual(account); + + // WHEN + accountService.authenticate(null); + + // THEN + expect(comp.account).toBeNull(); + }); + + it('Should hold current authenticated user in variable account if user is authenticated before page load', () => { + // GIVEN + accountService.authenticate(account); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(comp.account).toEqual(account); + + // WHEN + accountService.authenticate(null); + + // THEN + expect(comp.account).toBeNull(); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.ts new file mode 100644 index 0000000..af49de7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/navbar/navbar.component.ts @@ -0,0 +1,81 @@ +import { Component, OnInit } from '@angular/core'; +import { Router, RouterModule } from '@angular/router'; +import { TranslateService } from '@ngx-translate/core'; + +import { StateStorageService } from 'app/core/auth/state-storage.service'; +import SharedModule from 'app/shared/shared.module'; +import HasAnyAuthorityDirective from 'app/shared/auth/has-any-authority.directive'; +import { VERSION } from 'app/app.constants'; +import { LANGUAGES } from 'app/config/language.constants'; +import { Account } from 'app/core/auth/account.model'; +import { AccountService } from 'app/core/auth/account.service'; +import { LoginService } from 'app/login/login.service'; +import { ProfileService } from 'app/layouts/profiles/profile.service'; +import { EntityNavbarItems } from 'app/entities/entity-navbar-items'; +import ActiveMenuDirective from './active-menu.directive'; +import NavbarItem from './navbar-item.model'; + +@Component({ + standalone: true, + selector: 'jhi-navbar', + templateUrl: './navbar.component.html', + styleUrls: ['./navbar.component.scss'], + imports: [RouterModule, SharedModule, HasAnyAuthorityDirective, ActiveMenuDirective], +}) +export default class NavbarComponent implements OnInit { + inProduction?: boolean; + isNavbarCollapsed = true; + languages = LANGUAGES; + openAPIEnabled?: boolean; + version = ''; + account: Account | null = null; + entitiesNavbarItems: NavbarItem[] = []; + + constructor( + private loginService: LoginService, + private translateService: TranslateService, + private stateStorageService: StateStorageService, + private accountService: AccountService, + private profileService: ProfileService, + private router: Router, + ) { + if (VERSION) { + this.version = VERSION.toLowerCase().startsWith('v') ? VERSION : `v${VERSION}`; + } + } + + ngOnInit(): void { + this.entitiesNavbarItems = EntityNavbarItems; + this.profileService.getProfileInfo().subscribe(profileInfo => { + this.inProduction = profileInfo.inProduction; + this.openAPIEnabled = profileInfo.openAPIEnabled; + }); + + this.accountService.getAuthenticationState().subscribe(account => { + this.account = account; + }); + } + + changeLanguage(languageKey: string): void { + this.stateStorageService.storeLocale(languageKey); + this.translateService.use(languageKey); + } + + collapseNavbar(): void { + this.isNavbarCollapsed = true; + } + + login(): void { + this.router.navigate(['/login']); + } + + logout(): void { + this.collapseNavbar(); + this.loginService.logout(); + this.router.navigate(['']); + } + + toggleNavbar(): void { + this.isNavbarCollapsed = !this.isNavbarCollapsed; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.scss new file mode 100644 index 0000000..88b0602 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.scss @@ -0,0 +1,25 @@ +/* ========================================================================== +Developement Ribbon +========================================================================== */ +.ribbon { + background-color: rgba(170, 0, 0, 0.5); + overflow: hidden; + position: absolute; + top: 40px; + white-space: nowrap; + width: 15em; + z-index: 9999; + pointer-events: none; + opacity: 0.75; + a { + color: #fff; + display: block; + font-weight: 400; + margin: 1px 0; + padding: 10px 50px; + text-align: center; + text-decoration: none; + text-shadow: 0 0 5px #444; + pointer-events: none; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.spec.ts new file mode 100644 index 0000000..4d5176c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.spec.ts @@ -0,0 +1,39 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { of } from 'rxjs'; + +import { ProfileInfo } from 'app/layouts/profiles/profile-info.model'; +import { ProfileService } from 'app/layouts/profiles/profile.service'; + +import PageRibbonComponent from './page-ribbon.component'; + +describe('Page Ribbon Component', () => { + let comp: PageRibbonComponent; + let fixture: ComponentFixture; + let profileService: ProfileService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HttpClientTestingModule, PageRibbonComponent], + }) + .overrideTemplate(PageRibbonComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(PageRibbonComponent); + comp = fixture.componentInstance; + profileService = TestBed.inject(ProfileService); + }); + + it('Should call profileService.getProfileInfo on init', () => { + // GIVEN + jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(new ProfileInfo())); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(profileService.getProfileInfo).toHaveBeenCalled(); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts new file mode 100644 index 0000000..1cd00f6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/page-ribbon.component.ts @@ -0,0 +1,27 @@ +import { Component, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import SharedModule from 'app/shared/shared.module'; +import { ProfileService } from './profile.service'; + +@Component({ + standalone: true, + selector: 'jhi-page-ribbon', + template: ` + + `, + styleUrls: ['./page-ribbon.component.scss'], + imports: [SharedModule], +}) +export default class PageRibbonComponent implements OnInit { + ribbonEnv$?: Observable; + + constructor(private profileService: ProfileService) {} + + ngOnInit(): void { + this.ribbonEnv$ = this.profileService.getProfileInfo().pipe(map(profileInfo => profileInfo.ribbonEnv)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile-info.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile-info.model.ts new file mode 100644 index 0000000..14e920f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile-info.model.ts @@ -0,0 +1,15 @@ +export interface InfoResponse { + 'display-ribbon-on-profiles'?: string; + git?: any; + build?: any; + activeProfiles?: string[]; +} + +export class ProfileInfo { + constructor( + public activeProfiles?: string[], + public ribbonEnv?: string, + public inProduction?: boolean, + public openAPIEnabled?: boolean, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile.service.ts new file mode 100644 index 0000000..ec11dd3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/layouts/profiles/profile.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { map, shareReplay } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +import { ApplicationConfigService } from 'app/core/config/application-config.service'; +import { ProfileInfo, InfoResponse } from './profile-info.model'; + +@Injectable({ providedIn: 'root' }) +export class ProfileService { + private infoUrl = this.applicationConfigService.getEndpointFor('management/info'); + private profileInfo$?: Observable; + + constructor( + private http: HttpClient, + private applicationConfigService: ApplicationConfigService, + ) {} + + getProfileInfo(): Observable { + if (this.profileInfo$) { + return this.profileInfo$; + } + + this.profileInfo$ = this.http.get(this.infoUrl).pipe( + map((response: InfoResponse) => { + const profileInfo: ProfileInfo = { + activeProfiles: response.activeProfiles, + inProduction: response.activeProfiles?.includes('prod'), + openAPIEnabled: response.activeProfiles?.includes('api-docs'), + }; + if (response.activeProfiles && response['display-ribbon-on-profiles']) { + const displayRibbonOnProfiles = response['display-ribbon-on-profiles'].split(','); + const ribbonProfiles = displayRibbonOnProfiles.filter(profile => response.activeProfiles?.includes(profile)); + if (ribbonProfiles.length > 0) { + profileInfo.ribbonEnv = ribbonProfiles[0]; + } + } + return profileInfo; + }), + shareReplay(), + ); + return this.profileInfo$; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.html new file mode 100644 index 0000000..f4e6b31 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.html @@ -0,0 +1,57 @@ +
+
+
+

Sign in

+
+ Failed to sign in! Please check your credentials and try again. +
+
+
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ + +
+ You don't have an account yet? + Register a new account +
+
+
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.spec.ts new file mode 100644 index 0000000..da3b79c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.spec.ts @@ -0,0 +1,152 @@ +jest.mock('app/core/auth/account.service'); +jest.mock('app/login/login.service'); + +import { ElementRef } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { FormBuilder } from '@angular/forms'; +import { Router, Navigation } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; +import { of, throwError } from 'rxjs'; + +import { AccountService } from 'app/core/auth/account.service'; + +import { LoginService } from './login.service'; +import LoginComponent from './login.component'; + +describe('LoginComponent', () => { + let comp: LoginComponent; + let fixture: ComponentFixture; + let mockRouter: Router; + let mockAccountService: AccountService; + let mockLoginService: LoginService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [RouterTestingModule.withRoutes([]), LoginComponent], + providers: [ + FormBuilder, + AccountService, + { + provide: LoginService, + useValue: { + login: jest.fn(() => of({})), + }, + }, + ], + }) + .overrideTemplate(LoginComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LoginComponent); + comp = fixture.componentInstance; + mockRouter = TestBed.inject(Router); + jest.spyOn(mockRouter, 'navigate').mockImplementation(() => Promise.resolve(true)); + mockLoginService = TestBed.inject(LoginService); + mockAccountService = TestBed.inject(AccountService); + }); + + describe('ngOnInit', () => { + it('Should call accountService.identity on Init', () => { + // GIVEN + mockAccountService.identity = jest.fn(() => of(null)); + mockAccountService.getAuthenticationState = jest.fn(() => of(null)); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(mockAccountService.identity).toHaveBeenCalled(); + }); + + it('Should call accountService.isAuthenticated on Init', () => { + // GIVEN + mockAccountService.identity = jest.fn(() => of(null)); + + // WHEN + comp.ngOnInit(); + + // THEN + expect(mockAccountService.isAuthenticated).toHaveBeenCalled(); + }); + + it('should navigate to home page on Init if authenticated=true', () => { + // GIVEN + mockAccountService.identity = jest.fn(() => of(null)); + mockAccountService.getAuthenticationState = jest.fn(() => of(null)); + mockAccountService.isAuthenticated = () => true; + + // WHEN + comp.ngOnInit(); + + // THEN + expect(mockRouter.navigate).toHaveBeenCalledWith(['']); + }); + }); + + describe('ngAfterViewInit', () => { + it('should set focus to username input after the view has been initialized', () => { + // GIVEN + const node = { + focus: jest.fn(), + }; + comp.username = new ElementRef(node); + + // WHEN + comp.ngAfterViewInit(); + + // THEN + expect(node.focus).toHaveBeenCalled(); + }); + }); + + describe('login', () => { + it('should authenticate the user and navigate to home page', () => { + // GIVEN + const credentials = { + username: 'admin', + password: 'admin', + rememberMe: true, + }; + + comp.loginForm.patchValue({ + username: 'admin', + password: 'admin', + rememberMe: true, + }); + + // WHEN + comp.login(); + + // THEN + expect(comp.authenticationError).toEqual(false); + expect(mockLoginService.login).toHaveBeenCalledWith(credentials); + expect(mockRouter.navigate).toHaveBeenCalledWith(['']); + }); + + it('should authenticate the user but not navigate to home page if authentication process is already routing to cached url from localstorage', () => { + // GIVEN + jest.spyOn(mockRouter, 'getCurrentNavigation').mockReturnValue({} as Navigation); + + // WHEN + comp.login(); + + // THEN + expect(comp.authenticationError).toEqual(false); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + + it('should stay on login form and show error message on login error', () => { + // GIVEN + mockLoginService.login = jest.fn(() => throwError({})); + + // WHEN + comp.login(); + + // THEN + expect(comp.authenticationError).toEqual(true); + expect(mockRouter.navigate).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.ts new file mode 100644 index 0000000..73b19a9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.component.ts @@ -0,0 +1,58 @@ +import { Component, ViewChild, OnInit, AfterViewInit, ElementRef } from '@angular/core'; +import { FormGroup, FormControl, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { Router, RouterModule } from '@angular/router'; + +import SharedModule from 'app/shared/shared.module'; +import { LoginService } from 'app/login/login.service'; +import { AccountService } from 'app/core/auth/account.service'; + +@Component({ + selector: 'jhi-login', + standalone: true, + imports: [SharedModule, FormsModule, ReactiveFormsModule, RouterModule], + templateUrl: './login.component.html', +}) +export default class LoginComponent implements OnInit, AfterViewInit { + @ViewChild('username', { static: false }) + username!: ElementRef; + + authenticationError = false; + + loginForm = new FormGroup({ + username: new FormControl('', { nonNullable: true, validators: [Validators.required] }), + password: new FormControl('', { nonNullable: true, validators: [Validators.required] }), + rememberMe: new FormControl(false, { nonNullable: true, validators: [Validators.required] }), + }); + + constructor( + private accountService: AccountService, + private loginService: LoginService, + private router: Router, + ) {} + + ngOnInit(): void { + // if already authenticated then navigate to home page + this.accountService.identity().subscribe(() => { + if (this.accountService.isAuthenticated()) { + this.router.navigate(['']); + } + }); + } + + ngAfterViewInit(): void { + this.username.nativeElement.focus(); + } + + login(): void { + this.loginService.login(this.loginForm.getRawValue()).subscribe({ + next: () => { + this.authenticationError = false; + if (!this.router.getCurrentNavigation()) { + // There were no routing during login (eg from navigationToStoredUrl) + this.router.navigate(['']); + } + }, + error: () => (this.authenticationError = true), + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.model.ts new file mode 100644 index 0000000..10faab7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.model.ts @@ -0,0 +1,7 @@ +export class Login { + constructor( + public username: string, + public password: string, + public rememberMe: boolean, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.service.ts new file mode 100644 index 0000000..7b79a17 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/login/login.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs'; +import { mergeMap } from 'rxjs/operators'; + +import { Account } from 'app/core/auth/account.model'; +import { AccountService } from 'app/core/auth/account.service'; +import { AuthServerProvider } from 'app/core/auth/auth-jwt.service'; +import { Login } from './login.model'; + +@Injectable({ providedIn: 'root' }) +export class LoginService { + constructor( + private accountService: AccountService, + private authServerProvider: AuthServerProvider, + ) {} + + login(credentials: Login): Observable { + return this.authServerProvider.login(credentials).pipe(mergeMap(() => this.accountService.identity(true))); + } + + logout(): void { + this.authServerProvider.logout().subscribe({ complete: () => this.accountService.authenticate(null) }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.html new file mode 100644 index 0000000..76ff881 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.html @@ -0,0 +1,7 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.spec.ts new file mode 100644 index 0000000..02f160c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.spec.ts @@ -0,0 +1,159 @@ +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { HttpErrorResponse, HttpHeaders } from '@angular/common/http'; +import { TranslateModule } from '@ngx-translate/core'; + +import { EventManager } from 'app/core/util/event-manager.service'; +import { Alert, AlertService } from 'app/core/util/alert.service'; + +import { AlertErrorComponent } from './alert-error.component'; + +describe('Alert Error Component', () => { + let comp: AlertErrorComponent; + let fixture: ComponentFixture; + let eventManager: EventManager; + let alertService: AlertService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), AlertErrorComponent], + providers: [EventManager, AlertService], + }) + .overrideTemplate(AlertErrorComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AlertErrorComponent); + comp = fixture.componentInstance; + eventManager = TestBed.inject(EventManager); + alertService = TestBed.inject(AlertService); + alertService.addAlert = (alert: Alert, alerts?: Alert[]) => { + if (alerts) { + alerts.push(alert); + } + return alert; + }; + }); + + describe('Error Handling', () => { + it('Should display an alert on status 0', () => { + // GIVEN + eventManager.broadcast({ name: 'appApp.httpError', content: { status: 0 } }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('error.server.not.reachable'); + }); + + it('Should display an alert on status 404', () => { + // GIVEN + eventManager.broadcast({ name: 'appApp.httpError', content: { status: 404 } }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('error.url.not.found'); + }); + + it('Should display an alert on generic error', () => { + // GIVEN + eventManager.broadcast({ name: 'appApp.httpError', content: { error: { message: 'Error Message' } } }); + eventManager.broadcast({ name: 'appApp.httpError', content: { error: 'Second Error Message' } }); + // THEN + expect(comp.alerts.length).toBe(2); + expect(comp.alerts[0].translationKey).toBe('Error Message'); + expect(comp.alerts[1].translationKey).toBe('Second Error Message'); + }); + + it('Should display an alert on status 400 for generic error', () => { + // GIVEN + const response = new HttpErrorResponse({ + url: 'http://localhost:8080/api/foos', + headers: new HttpHeaders(), + status: 400, + statusText: 'Bad Request', + error: { + type: 'https://www.jhipster.tech/problem/constraint-violation', + title: 'Bad Request', + status: 400, + path: '/api/foos', + message: 'error.validation', + }, + }); + eventManager.broadcast({ name: 'appApp.httpError', content: response }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('error.validation'); + }); + + it('Should display an alert on status 400 for generic error without message', () => { + // GIVEN + const response = new HttpErrorResponse({ + url: 'http://localhost:8080/api/foos', + headers: new HttpHeaders(), + status: 400, + error: 'Bad Request', + }); + eventManager.broadcast({ name: 'appApp.httpError', content: response }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('Bad Request'); + }); + + it('Should display an alert on status 400 for invalid parameters', () => { + // GIVEN + const response = new HttpErrorResponse({ + url: 'http://localhost:8080/api/foos', + headers: new HttpHeaders(), + status: 400, + statusText: 'Bad Request', + error: { + type: 'https://www.jhipster.tech/problem/constraint-violation', + title: 'Method argument not valid', + status: 400, + path: '/api/foos', + message: 'error.validation', + fieldErrors: [{ objectName: 'foo', field: 'minField', message: 'Min' }], + }, + }); + eventManager.broadcast({ name: 'appApp.httpError', content: response }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('error.Size'); + }); + + it('Should display an alert on status 400 for error headers', () => { + // GIVEN + const response = new HttpErrorResponse({ + url: 'http://localhost:8080/api/foos', + headers: new HttpHeaders().append('app-error', 'Error Message').append('app-params', 'foo'), + status: 400, + statusText: 'Bad Request', + error: { + status: 400, + message: 'error.validation', + }, + }); + eventManager.broadcast({ name: 'appApp.httpError', content: response }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('Error Message'); + }); + + it('Should display an alert on status 500 with detail', () => { + // GIVEN + const response = new HttpErrorResponse({ + url: 'http://localhost:8080/api/foos', + headers: new HttpHeaders(), + status: 500, + statusText: 'Internal server error', + error: { + status: 500, + message: 'error.http.500', + detail: 'Detailed error message', + }, + }); + eventManager.broadcast({ name: 'appApp.httpError', content: response }); + // THEN + expect(comp.alerts.length).toBe(1); + expect(comp.alerts[0].translationKey).toBe('error.http.500'); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.ts new file mode 100644 index 0000000..204e650 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.component.ts @@ -0,0 +1,116 @@ +import { Component, OnDestroy } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; +import { Subscription } from 'rxjs'; +import { CommonModule } from '@angular/common'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { TranslateService } from '@ngx-translate/core'; + +import { Alert, AlertService } from 'app/core/util/alert.service'; +import { EventManager, EventWithContent } from 'app/core/util/event-manager.service'; +import { AlertError } from './alert-error.model'; + +@Component({ + standalone: true, + selector: 'jhi-alert-error', + templateUrl: './alert-error.component.html', + imports: [CommonModule, NgbModule], +}) +export class AlertErrorComponent implements OnDestroy { + alerts: Alert[] = []; + errorListener: Subscription; + httpErrorListener: Subscription; + + constructor( + private alertService: AlertService, + private eventManager: EventManager, + translateService: TranslateService, + ) { + this.errorListener = eventManager.subscribe('appApp.error', (response: EventWithContent | string) => { + const errorResponse = (response as EventWithContent).content; + this.addErrorAlert(errorResponse.message, errorResponse.key, errorResponse.params); + }); + + this.httpErrorListener = eventManager.subscribe('appApp.httpError', (response: EventWithContent | string) => { + const httpErrorResponse = (response as EventWithContent).content; + switch (httpErrorResponse.status) { + // connection refused, server not reachable + case 0: + this.addErrorAlert('Server not reachable', 'error.server.not.reachable'); + break; + + case 400: { + const arr = httpErrorResponse.headers.keys(); + let errorHeader: string | null = null; + let entityKey: string | null = null; + for (const entry of arr) { + if (entry.toLowerCase().endsWith('app-error')) { + errorHeader = httpErrorResponse.headers.get(entry); + } else if (entry.toLowerCase().endsWith('app-params')) { + entityKey = httpErrorResponse.headers.get(entry); + } + } + if (errorHeader) { + const alertData = entityKey ? { entityName: translateService.instant(`global.menu.entities.${entityKey}`) } : undefined; + this.addErrorAlert(errorHeader, errorHeader, alertData); + } else if (httpErrorResponse.error !== '' && httpErrorResponse.error.fieldErrors) { + const fieldErrors = httpErrorResponse.error.fieldErrors; + for (const fieldError of fieldErrors) { + if (['Min', 'Max', 'DecimalMin', 'DecimalMax'].includes(fieldError.message)) { + fieldError.message = 'Size'; + } + // convert 'something[14].other[4].id' to 'something[].other[].id' so translations can be written to it + const convertedField: string = fieldError.field.replace(/\[\d*\]/g, '[]'); + const fieldName: string = translateService.instant(`appApp.${fieldError.objectName as string}.${convertedField}`); + this.addErrorAlert(`Error on field "${fieldName}"`, `error.${fieldError.message as string}`, { fieldName }); + } + } else if (httpErrorResponse.error !== '' && httpErrorResponse.error.message) { + this.addErrorAlert( + httpErrorResponse.error.detail ?? httpErrorResponse.error.message, + httpErrorResponse.error.message, + httpErrorResponse.error.params, + ); + } else { + this.addErrorAlert(httpErrorResponse.error, httpErrorResponse.error); + } + break; + } + + case 404: + this.addErrorAlert('Not found', 'error.url.not.found'); + break; + + default: + if (httpErrorResponse.error !== '' && httpErrorResponse.error.message) { + this.addErrorAlert( + httpErrorResponse.error.detail ?? httpErrorResponse.error.message, + httpErrorResponse.error.message, + httpErrorResponse.error.params, + ); + } else { + this.addErrorAlert(httpErrorResponse.error, httpErrorResponse.error); + } + } + }); + } + + setClasses(alert: Alert): { [key: string]: boolean } { + const classes = { 'jhi-toast': Boolean(alert.toast) }; + if (alert.position) { + return { ...classes, [alert.position]: true }; + } + return classes; + } + + ngOnDestroy(): void { + this.eventManager.destroy(this.errorListener); + this.eventManager.destroy(this.httpErrorListener); + } + + close(alert: Alert): void { + alert.close?.(this.alerts); + } + + private addErrorAlert(message?: string, translationKey?: string, translationParams?: { [key: string]: unknown }): void { + this.alertService.addAlert({ type: 'danger', message, translationKey, translationParams }, this.alerts); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.model.ts new file mode 100644 index 0000000..cc8ca7d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert-error.model.ts @@ -0,0 +1,7 @@ +export class AlertError { + constructor( + public message: string, + public key?: string, + public params?: { [key: string]: unknown }, + ) {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.html new file mode 100644 index 0000000..76ff881 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.html @@ -0,0 +1,7 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.spec.ts new file mode 100644 index 0000000..79fe41f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.spec.ts @@ -0,0 +1,44 @@ +jest.mock('app/core/util/alert.service'); + +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; + +import { AlertService } from 'app/core/util/alert.service'; + +import { AlertComponent } from './alert.component'; + +describe('Alert Component', () => { + let comp: AlertComponent; + let fixture: ComponentFixture; + let mockAlertService: AlertService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [AlertComponent], + providers: [AlertService], + }) + .overrideTemplate(AlertComponent, '') + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AlertComponent); + comp = fixture.componentInstance; + mockAlertService = TestBed.inject(AlertService); + }); + + it('Should call alertService.get on init', () => { + // WHEN + comp.ngOnInit(); + + // THEN + expect(mockAlertService.get).toHaveBeenCalled(); + }); + + it('Should call alertService.clear on destroy', () => { + // WHEN + comp.ngOnDestroy(); + + // THEN + expect(mockAlertService.clear).toHaveBeenCalled(); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.ts new file mode 100644 index 0000000..098a90f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/alert/alert.component.ts @@ -0,0 +1,37 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { AlertService, Alert } from 'app/core/util/alert.service'; + +@Component({ + standalone: true, + selector: 'jhi-alert', + templateUrl: './alert.component.html', + imports: [CommonModule, NgbModule], +}) +export class AlertComponent implements OnInit, OnDestroy { + alerts: Alert[] = []; + + constructor(private alertService: AlertService) {} + + ngOnInit(): void { + this.alerts = this.alertService.get(); + } + + setClasses(alert: Alert): { [key: string]: boolean } { + const classes = { 'jhi-toast': Boolean(alert.toast) }; + if (alert.position) { + return { ...classes, [alert.position]: true }; + } + return classes; + } + + ngOnDestroy(): void { + this.alertService.clear(); + } + + close(alert: Alert): void { + alert.close?.(this.alerts); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.spec.ts new file mode 100644 index 0000000..3b8c21a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.spec.ts @@ -0,0 +1,131 @@ +jest.mock('app/core/auth/account.service'); + +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { Subject } from 'rxjs'; + +import { AccountService } from 'app/core/auth/account.service'; +import { Account } from 'app/core/auth/account.model'; + +import HasAnyAuthorityDirective from './has-any-authority.directive'; + +@Component({ + template: `
`, +}) +class TestHasAnyAuthorityDirectiveComponent { + @ViewChild('content', { static: false }) + content?: ElementRef; +} + +describe('HasAnyAuthorityDirective tests', () => { + let mockAccountService: AccountService; + const authenticationState = new Subject(); + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [HasAnyAuthorityDirective], + declarations: [TestHasAnyAuthorityDirectiveComponent], + providers: [AccountService], + }); + })); + + beforeEach(() => { + mockAccountService = TestBed.inject(AccountService); + mockAccountService.getAuthenticationState = jest.fn(() => authenticationState.asObservable()); + }); + + describe('set jhiHasAnyAuthority', () => { + it('should show restricted content to user if user has required role', () => { + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => true); + const fixture = TestBed.createComponent(TestHasAnyAuthorityDirectiveComponent); + const comp = fixture.componentInstance; + + // WHEN + fixture.detectChanges(); + + // THEN + expect(comp.content).toBeDefined(); + }); + + it('should not show restricted content to user if user has not required role', () => { + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => false); + const fixture = TestBed.createComponent(TestHasAnyAuthorityDirectiveComponent); + const comp = fixture.componentInstance; + + // WHEN + fixture.detectChanges(); + + // THEN + expect(comp.content).toBeUndefined(); + }); + }); + + describe('change authorities', () => { + it('should show or not show restricted content correctly if user authorities are changing', () => { + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => true); + const fixture = TestBed.createComponent(TestHasAnyAuthorityDirectiveComponent); + const comp = fixture.componentInstance; + + // WHEN + fixture.detectChanges(); + + // THEN + expect(comp.content).toBeDefined(); + + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => false); + + // WHEN + authenticationState.next(null); + fixture.detectChanges(); + + // THEN + expect(comp.content).toBeUndefined(); + + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => true); + + // WHEN + authenticationState.next(null); + fixture.detectChanges(); + + // THEN + expect(comp.content).toBeDefined(); + }); + }); + + describe('ngOnDestroy', () => { + it('should destroy authentication state subscription on component destroy', () => { + // GIVEN + mockAccountService.hasAnyAuthority = jest.fn(() => true); + const fixture = TestBed.createComponent(TestHasAnyAuthorityDirectiveComponent); + const div = fixture.debugElement.queryAllNodes(By.directive(HasAnyAuthorityDirective))[0]; + const hasAnyAuthorityDirective = div.injector.get(HasAnyAuthorityDirective); + + // WHEN + fixture.detectChanges(); + + // THEN + expect(mockAccountService.hasAnyAuthority).toHaveBeenCalled(); + + // WHEN + jest.clearAllMocks(); + authenticationState.next(null); + + // THEN + expect(mockAccountService.hasAnyAuthority).toHaveBeenCalled(); + + // WHEN + jest.clearAllMocks(); + hasAnyAuthorityDirective.ngOnDestroy(); + authenticationState.next(null); + + // THEN + expect(mockAccountService.hasAnyAuthority).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.ts new file mode 100644 index 0000000..2cec009 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/auth/has-any-authority.directive.ts @@ -0,0 +1,58 @@ +import { Directive, Input, TemplateRef, ViewContainerRef, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { AccountService } from 'app/core/auth/account.service'; + +/** + * @whatItDoes Conditionally includes an HTML element if current user has any + * of the authorities passed as the `expression`. + * + * @howToUse + * ``` + * ... + * + * ... + * ``` + */ +@Directive({ + standalone: true, + selector: '[jhiHasAnyAuthority]', +}) +export default class HasAnyAuthorityDirective implements OnDestroy { + private authorities!: string | string[]; + + private readonly destroy$ = new Subject(); + + constructor( + private accountService: AccountService, + private templateRef: TemplateRef, + private viewContainerRef: ViewContainerRef, + ) {} + + @Input() + set jhiHasAnyAuthority(value: string | string[]) { + this.authorities = value; + this.updateView(); + // Get notified each time authentication state changes. + this.accountService + .getAuthenticationState() + .pipe(takeUntil(this.destroy$)) + .subscribe(() => { + this.updateView(); + }); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private updateView(): void { + const hasAnyAuthority = this.accountService.hasAnyAuthority(this.authorities); + this.viewContainerRef.clear(); + if (hasAnyAuthority) { + this.viewContainerRef.createEmbeddedView(this.templateRef); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/duration.pipe.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/duration.pipe.ts new file mode 100644 index 0000000..fda99e3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/duration.pipe.ts @@ -0,0 +1,16 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import dayjs from 'dayjs/esm'; + +@Pipe({ + standalone: true, + name: 'duration', +}) +export default class DurationPipe implements PipeTransform { + transform(value: any): string { + if (value) { + return dayjs.duration(value).humanize(); + } + return ''; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.spec.ts new file mode 100644 index 0000000..bdb618e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.spec.ts @@ -0,0 +1,19 @@ +import dayjs from 'dayjs/esm'; + +import FormatMediumDatePipe from './format-medium-date.pipe'; + +describe('FormatMediumDatePipe', () => { + const formatMediumDatePipe = new FormatMediumDatePipe(); + + it('should return an empty string when receive undefined', () => { + expect(formatMediumDatePipe.transform(undefined)).toBe(''); + }); + + it('should return an empty string when receive null', () => { + expect(formatMediumDatePipe.transform(null)).toBe(''); + }); + + it('should format date like this D MMM YYYY', () => { + expect(formatMediumDatePipe.transform(dayjs('2020-11-16').locale('fr'))).toBe('16 Nov 2020'); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.ts new file mode 100644 index 0000000..96b679b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-date.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import dayjs from 'dayjs/esm'; + +@Pipe({ + standalone: true, + name: 'formatMediumDate', +}) +export default class FormatMediumDatePipe implements PipeTransform { + transform(day: dayjs.Dayjs | null | undefined): string { + return day ? day.format('D MMM YYYY') : ''; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.spec.ts new file mode 100644 index 0000000..c08aa47 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.spec.ts @@ -0,0 +1,19 @@ +import dayjs from 'dayjs/esm'; + +import FormatMediumDatetimePipe from './format-medium-datetime.pipe'; + +describe('FormatMediumDatePipe', () => { + const formatMediumDatetimePipe = new FormatMediumDatetimePipe(); + + it('should return an empty string when receive undefined', () => { + expect(formatMediumDatetimePipe.transform(undefined)).toBe(''); + }); + + it('should return an empty string when receive null', () => { + expect(formatMediumDatetimePipe.transform(null)).toBe(''); + }); + + it('should format date like this D MMM YYYY', () => { + expect(formatMediumDatetimePipe.transform(dayjs('2020-11-16').locale('fr'))).toBe('16 Nov 2020 00:00:00'); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.ts new file mode 100644 index 0000000..bd09cfb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/format-medium-datetime.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +import dayjs from 'dayjs/esm'; + +@Pipe({ + standalone: true, + name: 'formatMediumDatetime', +}) +export default class FormatMediumDatetimePipe implements PipeTransform { + transform(day: dayjs.Dayjs | null | undefined): string { + return day ? day.format('D MMM YYYY HH:mm:ss') : ''; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/index.ts new file mode 100644 index 0000000..5372ce8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/date/index.ts @@ -0,0 +1,3 @@ +export { default as DurationPipe } from './duration.pipe'; +export { default as FormatMediumDatePipe } from './format-medium-date.pipe'; +export { default as FormatMediumDatetimePipe } from './format-medium-datetime.pipe'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.html new file mode 100644 index 0000000..092a931 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.html @@ -0,0 +1,12 @@ +
+ Following filters are set + +
    + +
  • + {{ filterOption.name }}: {{ value }} + +
  • +
    +
+
diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.ts new file mode 100644 index 0000000..8117666 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.component.ts @@ -0,0 +1,21 @@ +import { Component, Input } from '@angular/core'; +import SharedModule from '../shared.module'; +import { IFilterOptions } from './filter.model'; + +@Component({ + selector: 'jhi-filter', + standalone: true, + imports: [SharedModule], + templateUrl: './filter.component.html', +}) +export default class FilterComponent { + @Input() filters!: IFilterOptions; + + clearAllFilters(): void { + this.filters.clear(); + } + + clearFilter(filterName: string, value: string): void { + this.filters.removeFilter(filterName, value); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.spec.ts new file mode 100644 index 0000000..92bb207 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.spec.ts @@ -0,0 +1,242 @@ +import { convertToParamMap, ParamMap, Params } from '@angular/router'; +import { FilterOptions, FilterOption } from './filter.model'; + +describe('FilterModel Tests', () => { + describe('FilterOption', () => { + let filterOption: FilterOption; + + beforeEach(() => { + filterOption = new FilterOption('foo', ['bar', 'bar2']); + }); + + it('nameAsQueryParam returns query key', () => { + expect(filterOption.nameAsQueryParam()).toEqual('filter[foo]'); + }); + + describe('addValue', () => { + it('adds multiples unique values and returns true', () => { + const ret = filterOption.addValue('bar2', 'bar3', 'bar4'); + expect(filterOption.values).toMatchObject(['bar', 'bar2', 'bar3', 'bar4']); + expect(ret).toBe(true); + }); + it("doesn't adds duplicated values and return false", () => { + const ret = filterOption.addValue('bar', 'bar2'); + expect(filterOption.values).toMatchObject(['bar', 'bar2']); + expect(ret).toBe(false); + }); + }); + + describe('removeValue', () => { + it('removes the exiting value and return true', () => { + const ret = filterOption.removeValue('bar'); + expect(filterOption.values).toMatchObject(['bar2']); + expect(ret).toBe(true); + }); + it("doesn't removes the value and return false", () => { + const ret = filterOption.removeValue('foo'); + expect(filterOption.values).toMatchObject(['bar', 'bar2']); + expect(ret).toBe(false); + }); + }); + + describe('equals', () => { + it('returns true to matching options', () => { + const otherFilterOption = new FilterOption(filterOption.name, filterOption.values.concat()); + expect(filterOption.equals(otherFilterOption)).toBe(true); + expect(otherFilterOption.equals(filterOption)).toBe(true); + }); + it('returns false to different name', () => { + const otherFilterOption = new FilterOption('bar', filterOption.values.concat()); + expect(filterOption.equals(otherFilterOption)).toBe(false); + expect(otherFilterOption.equals(filterOption)).toBe(false); + }); + it('returns false to different values', () => { + const otherFilterOption = new FilterOption('bar', []); + expect(filterOption.equals(otherFilterOption)).toBe(false); + expect(otherFilterOption.equals(filterOption)).toBe(false); + }); + }); + }); + + describe('FilterOptions', () => { + describe('hasAnyFilterSet', () => { + it('with empty options returns false', () => { + const filters = new FilterOptions(); + expect(filters.hasAnyFilterSet()).toBe(false); + }); + it('with options and empty values returns false', () => { + const filters = new FilterOptions([new FilterOption('foo'), new FilterOption('bar')]); + expect(filters.hasAnyFilterSet()).toBe(false); + }); + it('with option and value returns true', () => { + const filters = new FilterOptions([new FilterOption('foo', ['bar'])]); + expect(filters.hasAnyFilterSet()).toBe(true); + }); + }); + + describe('clear', () => { + it("removes empty filters and dosn't emit next element", () => { + const filters = new FilterOptions([new FilterOption('foo'), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + filters.clear(); + + expect(filters.filterChanges.next).not.toBeCalled(); + expect(filters.filterOptions).toMatchObject([]); + }); + it('removes empty filters and emits next element', () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + filters.clear(); + + expect(filters.filterChanges.next).toHaveBeenCalledTimes(1); + expect(filters.filterOptions).toMatchObject([]); + }); + }); + + describe('addFilter', () => { + it('adds a non existing FilterOption, returns true and emit next element', () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.addFilter('addedFilter', 'addedValue'); + + expect(result).toBe(true); + expect(filters.filterChanges.next).toHaveBeenCalledTimes(1); + expect(filters.filterOptions).toMatchObject([ + { name: 'foo', values: ['existingFoo1', 'existingFoo2'] }, + { name: 'addedFilter', values: ['addedValue'] }, + ]); + }); + it('adds a non existing value to FilterOption, returns true and emit next element', () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.addFilter('foo', 'addedValue1', 'addedValue2'); + + expect(result).toBe(true); + expect(filters.filterChanges.next).toHaveBeenCalledTimes(1); + expect(filters.filterOptions).toMatchObject([ + { name: 'foo', values: ['existingFoo1', 'existingFoo2', 'addedValue1', 'addedValue2'] }, + ]); + }); + it("doesn't add FilterOption values already added, returns false and doesn't emit next element", () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.addFilter('foo', 'existingFoo1', 'existingFoo2'); + + expect(result).toBe(false); + expect(filters.filterChanges.next).not.toBeCalled(); + expect(filters.filterOptions).toMatchObject([{ name: 'foo', values: ['existingFoo1', 'existingFoo2'] }]); + }); + }); + + describe('removeFilter', () => { + it('removes an existing FilterOptions and returns true', () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.removeFilter('foo', 'existingFoo1'); + + expect(result).toBe(true); + expect(filters.filterChanges.next).toHaveBeenCalledTimes(1); + expect(filters.filterOptions).toMatchObject([{ name: 'foo', values: ['existingFoo2'] }]); + }); + it("doesn't remove a non existing FilterOptions values returns false", () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.removeFilter('foo', 'nonExisting1'); + + expect(result).toBe(false); + expect(filters.filterChanges.next).not.toBeCalled(); + expect(filters.filterOptions).toMatchObject([{ name: 'foo', values: ['existingFoo1', 'existingFoo2'] }]); + }); + it("doesn't remove a non existing FilterOptions returns false", () => { + const filters = new FilterOptions([new FilterOption('foo', ['existingFoo1', 'existingFoo2']), new FilterOption('bar')]); + jest.spyOn(filters.filterChanges, 'next'); + + const result = filters.removeFilter('nonExisting', 'nonExisting1'); + + expect(result).toBe(false); + expect(filters.filterChanges.next).not.toBeCalled(); + expect(filters.filterOptions).toMatchObject([{ name: 'foo', values: ['existingFoo1', 'existingFoo2'] }]); + }); + }); + + describe('initializeFromParams', () => { + const oneValidParam: Params = { + test: 'blub', + 'filter[hello.in]': 'world', + 'filter[invalid': 'invalid', + filter_invalid2: 'invalid', + }; + + const noValidParam: Params = { + test: 'blub', + 'filter[invalid': 'invalid', + filter_invalid2: 'invalid', + }; + + const paramWithTwoValues: Params = { + 'filter[hello.in]': ['world', 'world2'], + }; + + const paramWithTwoKeys: Params = { + 'filter[hello.in]': ['world', 'world2'], + 'filter[hello.notIn]': ['world3', 'world4'], + }; + + it('should parse from Params if there are any and not emit next element', () => { + const filters: FilterOptions = new FilterOptions([new FilterOption('foo', ['bar'])]); + jest.spyOn(filters.filterChanges, 'next'); + const paramMap: ParamMap = convertToParamMap(oneValidParam); + + filters.initializeFromParams(paramMap); + + expect(filters.filterChanges.next).not.toHaveBeenCalled(); + expect(filters.filterOptions).toMatchObject([{ name: 'hello.in', values: ['world'] }]); + }); + + it('should parse from Params and have none if there are none', () => { + const filters: FilterOptions = new FilterOptions(); + const paramMap: ParamMap = convertToParamMap(noValidParam); + jest.spyOn(filters.filterChanges, 'next'); + + filters.initializeFromParams(paramMap); + + expect(filters.filterChanges.next).not.toHaveBeenCalled(); + expect(filters.filterOptions).toMatchObject([]); + }); + + it('should parse from Params and have a parameter with 2 values and one additional value', () => { + const filters: FilterOptions = new FilterOptions([new FilterOption('hello.in', ['world'])]); + jest.spyOn(filters.filterChanges, 'next'); + + const paramMap: ParamMap = convertToParamMap(paramWithTwoValues); + + filters.initializeFromParams(paramMap); + + expect(filters.filterChanges.next).not.toHaveBeenCalled(); + expect(filters.filterOptions).toMatchObject([{ name: 'hello.in', values: ['world', 'world2'] }]); + }); + + it('should parse from Params and have a parameter with 2 keys', () => { + const filters: FilterOptions = new FilterOptions(); + jest.spyOn(filters.filterChanges, 'next'); + + const paramMap: ParamMap = convertToParamMap(paramWithTwoKeys); + + filters.initializeFromParams(paramMap); + + expect(filters.filterChanges.next).not.toHaveBeenCalled(); + expect(filters.filterOptions).toMatchObject([ + { name: 'hello.in', values: ['world', 'world2'] }, + { name: 'hello.notIn', values: ['world3', 'world4'] }, + ]); + }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.ts new file mode 100644 index 0000000..794a0c0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/filter.model.ts @@ -0,0 +1,159 @@ +import { ParamMap } from '@angular/router'; +import { Subject } from 'rxjs'; + +export interface IFilterOptions { + readonly filterChanges: Subject; + get filterOptions(): IFilterOption[]; + hasAnyFilterSet(): boolean; + clear(): boolean; + initializeFromParams(params: ParamMap): boolean; + addFilter(name: string, ...values: string[]): boolean; + removeFilter(name: string, value: string): boolean; +} + +export interface IFilterOption { + name: string; + values: string[]; + nameAsQueryParam(): string; +} + +export class FilterOption implements IFilterOption { + constructor( + public name: string, + public values: string[] = [], + ) { + this.values = [...new Set(values)]; + } + + nameAsQueryParam(): string { + return 'filter[' + this.name + ']'; + } + + isSet(): boolean { + return this.values.length > 0; + } + + addValue(...values: string[]): boolean { + const missingValues = values.filter(value => value && !this.values.includes(value)); + if (missingValues.length > 0) { + this.values.push(...missingValues); + return true; + } + return false; + } + + removeValue(value: string): boolean { + const indexOf = this.values.indexOf(value); + if (indexOf === -1) { + return false; + } + + this.values.splice(indexOf, 1); + return true; + } + + clone(): FilterOption { + return new FilterOption(this.name, this.values.concat()); + } + + equals(other: IFilterOption): boolean { + return ( + this.name === other.name && + this.values.length === other.values.length && + this.values.every(thisValue => other.values.includes(thisValue)) && + other.values.every(otherValue => this.values.includes(otherValue)) + ); + } +} + +export class FilterOptions implements IFilterOptions { + readonly filterChanges: Subject = new Subject(); + private _filterOptions: FilterOption[]; + + constructor(filterOptions: FilterOption[] = []) { + this._filterOptions = filterOptions; + } + + get filterOptions(): FilterOption[] { + return this._filterOptions.filter(option => option.isSet()); + } + + hasAnyFilterSet(): boolean { + return this._filterOptions.some(e => e.isSet()); + } + + clear(): boolean { + const hasFields = this.hasAnyFilterSet(); + this._filterOptions = []; + if (hasFields) { + this.changed(); + } + return hasFields; + } + + initializeFromParams(params: ParamMap): boolean { + const oldFilters: FilterOptions = this.clone(); + + this._filterOptions = []; + + const filterRegex = /filter\[(.+)\]/; + params.keys + .filter(paramKey => filterRegex.test(paramKey)) + .forEach(matchingParam => { + const matches = filterRegex.exec(matchingParam); + if (matches && matches.length > 1) { + this.getFilterOptionByName(matches[1], true).addValue(...params.getAll(matchingParam)); + } + }); + + if (oldFilters.equals(this)) { + return false; + } + return true; + } + + addFilter(name: string, ...values: string[]): boolean { + if (this.getFilterOptionByName(name, true).addValue(...values)) { + this.changed(); + return true; + } + return false; + } + + removeFilter(name: string, value: string): boolean { + if (this.getFilterOptionByName(name)?.removeValue(value)) { + this.changed(); + return true; + } + return false; + } + + protected changed(): void { + this.filterChanges.next(this.filterOptions.map(option => option.clone())); + } + + protected equals(other: FilterOptions): boolean { + const thisFilters = this.filterOptions; + const otherFilters = other.filterOptions; + if (thisFilters.length !== otherFilters.length) { + return false; + } + return thisFilters.every(option => other.getFilterOptionByName(option.name)?.equals(option)); + } + + protected clone(): FilterOptions { + return new FilterOptions(this.filterOptions.map(option => new FilterOption(option.name, option.values.concat()))); + } + + protected getFilterOptionByName(name: string, add: true): FilterOption; + protected getFilterOptionByName(name: string, add: false): FilterOption | null; + protected getFilterOptionByName(name: string): FilterOption | null; + protected getFilterOptionByName(name: string, add = false): FilterOption | null { + const addOption = (option: FilterOption): FilterOption => { + this._filterOptions.push(option); + return option; + }; + + return this._filterOptions.find(thisOption => thisOption.name === name) ?? (add ? addOption(new FilterOption(name)) : null); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/index.ts new file mode 100644 index 0000000..ae0af5a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/filter/index.ts @@ -0,0 +1,2 @@ +export { default as FilterComponent } from './filter.component'; +export * from './filter.model'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/find-language-from-key.pipe.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/find-language-from-key.pipe.ts new file mode 100644 index 0000000..660b429 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/find-language-from-key.pipe.ts @@ -0,0 +1,18 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + standalone: true, + name: 'findLanguageFromKey', +}) +export default class FindLanguageFromKeyPipe implements PipeTransform { + private languages: { [key: string]: { name: string; rtl?: boolean } } = { + en: { name: 'English' }, + fr: { name: 'Français' }, + de: { name: 'Deutsch' }, + // jhipster-needle-i18n-language-key-pipe - JHipster will add/remove languages in this object + }; + + transform(lang: string): string { + return this.languages[lang].name; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/index.ts new file mode 100644 index 0000000..3446ac2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/index.ts @@ -0,0 +1,2 @@ +export { default as TranslateDirective } from './translate.directive'; +export { default as FindLanguageFromKeyPipe } from './find-language-from-key.pipe'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.spec.ts new file mode 100644 index 0000000..b7b5657 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.spec.ts @@ -0,0 +1,35 @@ +import { Component } from '@angular/core'; +import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; +import { TranslateModule, TranslateService } from '@ngx-translate/core'; + +import TranslateDirective from './translate.directive'; + +@Component({ + template: `
`, +}) +class TestTranslateDirectiveComponent {} + +describe('TranslateDirective Tests', () => { + let fixture: ComponentFixture; + let translateService: TranslateService; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [TranslateModule.forRoot(), TranslateDirective], + declarations: [TestTranslateDirectiveComponent], + }); + })); + + beforeEach(() => { + translateService = TestBed.inject(TranslateService); + fixture = TestBed.createComponent(TestTranslateDirectiveComponent); + }); + + it('should change HTML', () => { + const spy = jest.spyOn(translateService, 'get'); + + fixture.detectChanges(); + + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.ts new file mode 100644 index 0000000..93f0f2f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translate.directive.ts @@ -0,0 +1,55 @@ +import { Input, Directive, ElementRef, OnChanges, OnInit, OnDestroy } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +import { translationNotFoundMessage } from 'app/config/translation.config'; + +/** + * A wrapper directive on top of the translate pipe as the inbuilt translate directive from ngx-translate is too verbose and buggy + */ +@Directive({ + standalone: true, + selector: '[jhiTranslate]', +}) +export default class TranslateDirective implements OnChanges, OnInit, OnDestroy { + @Input() jhiTranslate!: string; + @Input() translateValues?: { [key: string]: unknown }; + + private readonly directiveDestroyed = new Subject(); + + constructor( + private el: ElementRef, + private translateService: TranslateService, + ) {} + + ngOnInit(): void { + this.translateService.onLangChange.pipe(takeUntil(this.directiveDestroyed)).subscribe(() => { + this.getTranslation(); + }); + this.translateService.onTranslationChange.pipe(takeUntil(this.directiveDestroyed)).subscribe(() => { + this.getTranslation(); + }); + } + + ngOnChanges(): void { + this.getTranslation(); + } + + ngOnDestroy(): void { + this.directiveDestroyed.next(null); + this.directiveDestroyed.complete(); + } + + private getTranslation(): void { + this.translateService + .get(this.jhiTranslate, this.translateValues) + .pipe(takeUntil(this.directiveDestroyed)) + .subscribe({ + next: value => { + this.el.nativeElement.innerHTML = value; + }, + error: () => `${translationNotFoundMessage}[${this.jhiTranslate}]`, + }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translation.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translation.module.ts new file mode 100644 index 0000000..e6f1a6c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/language/translation.module.ts @@ -0,0 +1,32 @@ +import { NgModule } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { TranslateModule, TranslateService, TranslateLoader, MissingTranslationHandler } from '@ngx-translate/core'; +import { translatePartialLoader, missingTranslationHandler } from 'app/config/translation.config'; +import { StateStorageService } from 'app/core/auth/state-storage.service'; + +@NgModule({ + imports: [ + TranslateModule.forRoot({ + loader: { + provide: TranslateLoader, + useFactory: translatePartialLoader, + deps: [HttpClient], + }, + missingTranslationHandler: { + provide: MissingTranslationHandler, + useFactory: missingTranslationHandler, + }, + }), + ], +}) +export class TranslationModule { + constructor( + private translateService: TranslateService, + private stateStorageService: StateStorageService, + ) { + this.translateService.setDefaultLang('en'); + // if user have changed language and navigates away from the application and back to the application then use previously choosed language + const langKey = this.stateStorageService.getLocale() ?? 'en'; + this.translateService.use(langKey); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/index.ts new file mode 100644 index 0000000..395ed88 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/index.ts @@ -0,0 +1 @@ +export { default as ItemCountComponent } from './item-count.component'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.spec.ts new file mode 100644 index 0000000..9e91b1d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.spec.ts @@ -0,0 +1,67 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; + +import TranslateDirective from 'app/shared/language/translate.directive'; + +import ItemCountComponent from './item-count.component'; + +describe('ItemCountComponent test', () => { + let comp: ItemCountComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [ItemCountComponent, TranslateModule.forRoot(), TranslateDirective], + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ItemCountComponent); + comp = fixture.componentInstance; + }); + + describe('UI logic tests', () => { + it('should initialize with undefined', () => { + expect(comp.first).toBeUndefined(); + expect(comp.second).toBeUndefined(); + expect(comp.total).toBeUndefined(); + }); + + it('should set calculated numbers to undefined if the page value is not yet defined', () => { + // GIVEN + comp.params = { page: undefined, totalItems: 0, itemsPerPage: 10 }; + + // THEN + expect(comp.first).toBeUndefined(); + expect(comp.second).toBeUndefined(); + }); + + it('should change the content on page change', () => { + // GIVEN + comp.params = { page: 1, totalItems: 100, itemsPerPage: 10 }; + + // THEN + expect(comp.first).toBe(1); + expect(comp.second).toBe(10); + expect(comp.total).toBe(100); + + // GIVEN + comp.params = { page: 2, totalItems: 100, itemsPerPage: 10 }; + + // THEN + expect(comp.first).toBe(11); + expect(comp.second).toBe(20); + expect(comp.total).toBe(100); + }); + + it('should set the second number to totalItems if this is the last page which contains less than itemsPerPage items', () => { + // GIVEN + comp.params = { page: 2, totalItems: 16, itemsPerPage: 10 }; + + // THEN + expect(comp.first).toBe(11); + expect(comp.second).toBe(16); + expect(comp.total).toBe(16); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.ts new file mode 100644 index 0000000..0ac3521 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/pagination/item-count.component.ts @@ -0,0 +1,34 @@ +import { Component, Input } from '@angular/core'; +import TranslateDirective from '../language/translate.directive'; + +/** + * A component that will take care of item count statistics of a pagination. + */ +@Component({ + standalone: true, + selector: 'jhi-item-count', + template: `
`, + imports: [TranslateDirective], +}) +export default class ItemCountComponent { + /** + * @param params Contains parameters for component: + * page Current page number + * totalItems Total number of items + * itemsPerPage Number of items per page + */ + @Input() set params(params: { page?: number; totalItems?: number; itemsPerPage?: number }) { + if (params.page && params.totalItems !== undefined && params.itemsPerPage) { + this.first = (params.page - 1) * params.itemsPerPage + 1; + this.second = params.page * params.itemsPerPage < params.totalItems ? params.page * params.itemsPerPage : params.totalItems; + } else { + this.first = undefined; + this.second = undefined; + } + this.total = params.totalItems; + } + + first?: number; + second?: number; + total?: number; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/shared.module.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/shared.module.ts new file mode 100644 index 0000000..6511f7b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/shared.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; +import { TranslateModule } from '@ngx-translate/core'; + +import FindLanguageFromKeyPipe from './language/find-language-from-key.pipe'; +import TranslateDirective from './language/translate.directive'; +import { AlertComponent } from './alert/alert.component'; +import { AlertErrorComponent } from './alert/alert-error.component'; + +/** + * Application wide Module + */ +@NgModule({ + imports: [AlertComponent, AlertErrorComponent, FindLanguageFromKeyPipe, TranslateDirective], + exports: [ + CommonModule, + NgbModule, + FontAwesomeModule, + AlertComponent, + AlertErrorComponent, + TranslateModule, + FindLanguageFromKeyPipe, + TranslateDirective, + ], +}) +export default class SharedModule {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/index.ts new file mode 100644 index 0000000..1a04bec --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/index.ts @@ -0,0 +1,2 @@ +export { default as SortDirective } from './sort.directive'; +export { default as SortByDirective } from './sort-by.directive'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.spec.ts new file mode 100644 index 0000000..51fef72 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.spec.ts @@ -0,0 +1,140 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { FaIconComponent, FaIconLibrary } from '@fortawesome/angular-fontawesome'; +import { fas, faSort, faSortDown, faSortUp } from '@fortawesome/free-solid-svg-icons'; + +import SortByDirective from './sort-by.directive'; +import SortDirective from './sort.directive'; + +@Component({ + template: ` + + + + + + +
ID
+ `, +}) +class TestSortByDirectiveComponent { + predicate?: string; + ascending?: boolean; + sortAllowed = true; + transition = jest.fn(); + + constructor(library: FaIconLibrary) { + library.addIconPacks(fas); + library.addIcons(faSort, faSortDown, faSortUp); + } +} + +describe('Directive: SortByDirective', () => { + let component: TestSortByDirectiveComponent; + let fixture: ComponentFixture; + let tableHead: DebugElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SortDirective, SortByDirective], + declarations: [TestSortByDirectiveComponent, FaIconComponent], + }); + fixture = TestBed.createComponent(TestSortByDirectiveComponent); + component = fixture.componentInstance; + tableHead = fixture.debugElement.query(By.directive(SortByDirective)); + }); + + it('should initialize predicate, order, icon when initial component predicate differs from column predicate', () => { + // GIVEN + component.predicate = 'id'; + const sortByDirective = tableHead.injector.get(SortByDirective); + + // WHEN + fixture.detectChanges(); + + // THEN + expect(sortByDirective.jhiSortBy).toEqual('name'); + expect(component.predicate).toEqual('id'); + expect(sortByDirective.iconComponent?.icon).toEqual('sort'); + expect(component.transition).toHaveBeenCalledTimes(0); + }); + + it('should initialize predicate, order, icon when initial component predicate is same as column predicate', () => { + // GIVEN + component.predicate = 'name'; + component.ascending = true; + const sortByDirective = tableHead.injector.get(SortByDirective); + + // WHEN + fixture.detectChanges(); + + // THEN + expect(sortByDirective.jhiSortBy).toEqual('name'); + expect(component.predicate).toEqual('name'); + expect(component.ascending).toEqual(true); + expect(sortByDirective.iconComponent?.icon).toEqual(faSortUp.iconName); + expect(component.transition).toHaveBeenCalledTimes(0); + }); + + it('should update component predicate, order, icon when user clicks on column header', () => { + // GIVEN + component.predicate = 'name'; + component.ascending = true; + const sortByDirective = tableHead.injector.get(SortByDirective); + + // WHEN + fixture.detectChanges(); + tableHead.triggerEventHandler('click', null); + fixture.detectChanges(); + + // THEN + expect(component.predicate).toEqual('name'); + expect(component.ascending).toEqual(false); + expect(sortByDirective.iconComponent?.icon).toEqual(faSortDown.iconName); + expect(component.transition).toHaveBeenCalledTimes(1); + expect(component.transition).toHaveBeenCalledWith({ predicate: 'name', ascending: false }); + }); + + it('should update component predicate, order, icon when user double clicks on column header', () => { + // GIVEN + component.predicate = 'name'; + component.ascending = true; + const sortByDirective = tableHead.injector.get(SortByDirective); + + // WHEN + fixture.detectChanges(); + + tableHead.triggerEventHandler('click', null); + fixture.detectChanges(); + + tableHead.triggerEventHandler('click', null); + fixture.detectChanges(); + + // THEN + expect(component.predicate).toEqual('name'); + expect(component.ascending).toEqual(true); + expect(sortByDirective.iconComponent?.icon).toEqual(faSortUp.iconName); + expect(component.transition).toHaveBeenCalledTimes(2); + expect(component.transition).toHaveBeenNthCalledWith(1, { predicate: 'name', ascending: false }); + expect(component.transition).toHaveBeenNthCalledWith(2, { predicate: 'name', ascending: true }); + }); + + it('should not run sorting on click if sorting icon is hidden', () => { + // GIVEN + component.predicate = 'id'; + component.ascending = false; + component.sortAllowed = false; + + // WHEN + fixture.detectChanges(); + + tableHead.triggerEventHandler('click', null); + fixture.detectChanges(); + + // THEN + expect(component.predicate).toEqual('id'); + expect(component.ascending).toEqual(false); + expect(component.transition).not.toHaveBeenCalled(); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.ts new file mode 100644 index 0000000..1e8eda6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort-by.directive.ts @@ -0,0 +1,56 @@ +import { AfterContentInit, ContentChild, Directive, Host, HostListener, Input, OnDestroy } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { faSort, faSortDown, faSortUp, IconDefinition } from '@fortawesome/free-solid-svg-icons'; + +import SortDirective from './sort.directive'; + +@Directive({ + standalone: true, + selector: '[jhiSortBy]', +}) +export default class SortByDirective implements AfterContentInit, OnDestroy { + @Input() jhiSortBy!: T; + + @ContentChild(FaIconComponent, { static: false }) + iconComponent?: FaIconComponent; + + sortIcon = faSort; + sortAscIcon = faSortUp; + sortDescIcon = faSortDown; + + private readonly destroy$ = new Subject(); + + constructor(@Host() private sort: SortDirective) { + sort.predicateChange.pipe(takeUntil(this.destroy$)).subscribe(() => this.updateIconDefinition()); + sort.ascendingChange.pipe(takeUntil(this.destroy$)).subscribe(() => this.updateIconDefinition()); + } + + @HostListener('click') + onClick(): void { + if (this.iconComponent) { + this.sort.sort(this.jhiSortBy); + } + } + + ngAfterContentInit(): void { + this.updateIconDefinition(); + } + + ngOnDestroy(): void { + this.destroy$.next(); + this.destroy$.complete(); + } + + private updateIconDefinition(): void { + if (this.iconComponent) { + let icon: IconDefinition = this.sortIcon; + if (this.sort.predicate === this.jhiSortBy) { + icon = this.sort.ascending ? this.sortAscIcon : this.sortDescIcon; + } + this.iconComponent.icon = icon.iconName; + this.iconComponent.render(); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.spec.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.spec.ts new file mode 100644 index 0000000..5dc7b87 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.spec.ts @@ -0,0 +1,87 @@ +import { Component, DebugElement } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; + +import SortDirective from './sort.directive'; + +@Component({ + template: ` + + + + +
+ `, +}) +class TestSortDirectiveComponent { + predicate?: string; + ascending?: boolean; + transition = jest.fn(); +} + +describe('Directive: SortDirective', () => { + let component: TestSortDirectiveComponent; + let fixture: ComponentFixture; + let tableRow: DebugElement; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [SortDirective], + declarations: [TestSortDirectiveComponent], + }); + fixture = TestBed.createComponent(TestSortDirectiveComponent); + component = fixture.componentInstance; + tableRow = fixture.debugElement.query(By.directive(SortDirective)); + }); + + it('should update predicate, order and invoke sortChange function', () => { + // GIVEN + const sortDirective = tableRow.injector.get(SortDirective); + + // WHEN + fixture.detectChanges(); + sortDirective.sort('ID'); + + // THEN + expect(component.predicate).toEqual('ID'); + expect(component.ascending).toEqual(true); + expect(component.transition).toHaveBeenCalledTimes(1); + expect(component.transition).toHaveBeenCalledWith({ predicate: 'ID', ascending: true }); + }); + + it('should change sort order to descending when same field is sorted again', () => { + // GIVEN + const sortDirective = tableRow.injector.get(SortDirective); + + // WHEN + fixture.detectChanges(); + sortDirective.sort('ID'); + // sort again + sortDirective.sort('ID'); + + // THEN + expect(component.predicate).toEqual('ID'); + expect(component.ascending).toEqual(false); + expect(component.transition).toHaveBeenCalledTimes(2); + expect(component.transition).toHaveBeenNthCalledWith(1, { predicate: 'ID', ascending: true }); + expect(component.transition).toHaveBeenNthCalledWith(2, { predicate: 'ID', ascending: false }); + }); + + it('should change sort order to ascending when different field is sorted', () => { + // GIVEN + const sortDirective = tableRow.injector.get(SortDirective); + + // WHEN + fixture.detectChanges(); + sortDirective.sort('ID'); + // sort again + sortDirective.sort('NAME'); + + // THEN + expect(component.predicate).toEqual('NAME'); + expect(component.ascending).toEqual(true); + expect(component.transition).toHaveBeenCalledTimes(2); + expect(component.transition).toHaveBeenNthCalledWith(1, { predicate: 'ID', ascending: true }); + expect(component.transition).toHaveBeenNthCalledWith(2, { predicate: 'NAME', ascending: true }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.ts new file mode 100644 index 0000000..9bc4117 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.directive.ts @@ -0,0 +1,40 @@ +import { Directive, EventEmitter, Input, Output } from '@angular/core'; + +@Directive({ + standalone: true, + selector: '[jhiSort]', +}) +export default class SortDirective { + @Input() + get predicate(): T | undefined { + return this._predicate; + } + set predicate(predicate: T | undefined) { + this._predicate = predicate; + this.predicateChange.emit(predicate); + } + + @Input() + get ascending(): boolean | undefined { + return this._ascending; + } + set ascending(ascending: boolean | undefined) { + this._ascending = ascending; + this.ascendingChange.emit(ascending); + } + + @Output() predicateChange = new EventEmitter(); + @Output() ascendingChange = new EventEmitter(); + @Output() sortChange = new EventEmitter<{ predicate: T; ascending: boolean }>(); + + private _predicate?: T; + private _ascending?: boolean; + + sort(field: T): void { + this.ascending = field !== this.predicate ? true : !this.ascending; + this.predicate = field; + this.predicateChange.emit(field); + this.ascendingChange.emit(this.ascending); + this.sortChange.emit({ predicate: this.predicate, ascending: this.ascending }); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.service.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.service.ts new file mode 100644 index 0000000..d276059 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/app/shared/sort/sort.service.ts @@ -0,0 +1,13 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class SortService { + private collator = new Intl.Collator(undefined, { + numeric: true, + sensitivity: 'base', + }); + + public startSort(property: string, order: number): (a: any, b: any) => number { + return (a: any, b: any) => this.collator.compare(a[property], b[property]) * order; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/bootstrap.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/bootstrap.ts new file mode 100644 index 0000000..e5038d5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/bootstrap.ts @@ -0,0 +1,16 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { DEBUG_INFO_ENABLED } from './app/app.constants'; +import { AppModule } from './app/app.module'; + +// disable debug data on prod profile to improve performance +if (!DEBUG_INFO_ENABLED) { + enableProdMode(); +} + +platformBrowserDynamic() + .bootstrapModule(AppModule, { preserveWhitespaces: true }) + // eslint-disable-next-line no-console + .then(() => console.log('Application started')) + .catch(err => console.error(err)); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/css/loading.css b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/css/loading.css new file mode 100644 index 0000000..678e7b6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/css/loading.css @@ -0,0 +1,152 @@ +@keyframes lds-pacman-1 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 50% { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + } + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} +@-webkit-keyframes lds-pacman-1 { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 50% { + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); + } + 100% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } +} +@keyframes lds-pacman-2 { + 0% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } + 50% { + -webkit-transform: rotate(225deg); + transform: rotate(225deg); + } + 100% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } +} +@-webkit-keyframes lds-pacman-2 { + 0% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } + 50% { + -webkit-transform: rotate(225deg); + transform: rotate(225deg); + } + 100% { + -webkit-transform: rotate(180deg); + transform: rotate(180deg); + } +} +@keyframes lds-pacman-3 { + 0% { + -webkit-transform: translate(190px, 0); + transform: translate(190px, 0); + opacity: 0; + } + 20% { + opacity: 1; + } + 100% { + -webkit-transform: translate(70px, 0); + transform: translate(70px, 0); + opacity: 1; + } +} +@-webkit-keyframes lds-pacman-3 { + 0% { + -webkit-transform: translate(190px, 0); + transform: translate(190px, 0); + opacity: 0; + } + 20% { + opacity: 1; + } + 100% { + -webkit-transform: translate(70px, 0); + transform: translate(70px, 0); + opacity: 1; + } +} + +.app-loading { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + top: 10em; +} +.app-loading p { + display: block; + font-size: 1.17em; + margin-inline-start: 0px; + margin-inline-end: 0px; + font-weight: normal; +} + +.app-loading .lds-pacman { + position: relative; + margin: auto; + width: 200px !important; + height: 200px !important; + -webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px); + transform: translate(-100px, -100px) scale(1) translate(100px, 100px); +} +.app-loading .lds-pacman > div:nth-child(2) div { + position: absolute; + top: 40px; + left: 40px; + width: 120px; + height: 60px; + border-radius: 120px 120px 0 0; + background: #bbcedd; + -webkit-animation: lds-pacman-1 1s linear infinite; + animation: lds-pacman-1 1s linear infinite; + -webkit-transform-origin: 60px 60px; + transform-origin: 60px 60px; +} +.app-loading .lds-pacman > div:nth-child(2) div:nth-child(2) { + -webkit-animation: lds-pacman-2 1s linear infinite; + animation: lds-pacman-2 1s linear infinite; +} +.app-loading .lds-pacman > div:nth-child(1) div { + position: absolute; + top: 97px; + left: -8px; + width: 24px; + height: 10px; + background-image: url('/content/images/logo-jhipster.png'); + background-size: contain; + -webkit-animation: lds-pacman-3 1s linear infinite; + animation: lds-pacman-3 1.5s linear infinite; +} +.app-loading .lds-pacman > div:nth-child(1) div:nth-child(1) { + -webkit-animation-delay: -0.67s; + animation-delay: -1s; +} +.app-loading .lds-pacman > div:nth-child(1) div:nth-child(2) { + -webkit-animation-delay: -0.33s; + animation-delay: -0.5s; +} +.app-loading .lds-pacman > div:nth-child(1) div:nth-child(3) { + -webkit-animation-delay: 0s; + animation-delay: 0s; +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0.svg b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0.svg new file mode 100644 index 0000000..d6df83c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-192.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-192.png new file mode 100644 index 0000000..6d90ab3 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-192.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-256.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-256.png new file mode 100644 index 0000000..8e99bfe Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-256.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-384.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-384.png new file mode 100644 index 0000000..c7ca460 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-384.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-512.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-512.png new file mode 100644 index 0000000..7e0b843 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_0_head-512.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1.svg b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1.svg new file mode 100644 index 0000000..e3a0f3d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-192.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-192.png new file mode 100644 index 0000000..ac5f2a0 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-192.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-256.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-256.png new file mode 100644 index 0000000..443822e Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-256.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-384.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-384.png new file mode 100644 index 0000000..4a5e9fe Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-384.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-512.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-512.png new file mode 100644 index 0000000..66c625c Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_1_head-512.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2.svg b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2.svg new file mode 100644 index 0000000..51c6a5a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-192.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-192.png new file mode 100644 index 0000000..24baf78 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-192.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-256.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-256.png new file mode 100644 index 0000000..7b25f52 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-256.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-384.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-384.png new file mode 100644 index 0000000..e89e120 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-384.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-512.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-512.png new file mode 100644 index 0000000..3c0e6cb Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_2_head-512.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3.svg b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3.svg new file mode 100644 index 0000000..cc0d01f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-192.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-192.png new file mode 100644 index 0000000..b1e4fb3 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-192.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-256.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-256.png new file mode 100644 index 0000000..aa058c7 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-256.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-384.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-384.png new file mode 100644 index 0000000..1d10bd5 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-384.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-512.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-512.png new file mode 100644 index 0000000..e719f1d Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/jhipster_family_member_3_head-512.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/logo-jhipster.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/logo-jhipster.png new file mode 100644 index 0000000..6a005bf Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/images/logo-jhipster.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/_bootstrap-variables.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/_bootstrap-variables.scss new file mode 100644 index 0000000..89712f7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/_bootstrap-variables.scss @@ -0,0 +1,45 @@ +/* + * Bootstrap overrides https://getbootstrap.com/docs/5.1/customize/sass/ + * All values defined in bootstrap source + * https://github.com/twbs/bootstrap/blob/v5.1.3/scss/_variables.scss can be overwritten here + * Make sure not to add !default to values here + */ + +// Colors: +// Grayscale and brand colors for use across Bootstrap. + +$primary: #3e8acc; +$success: #28a745; +$info: #17a2b8; +$warning: #ffc107; +$danger: #dc3545; + +// Options: +// Quickly modify global styling by enabling or disabling optional features. +$enable-rounded: true; +$enable-shadows: false; +$enable-gradients: false; +$enable-transitions: true; +$enable-hover-media-query: false; +$enable-grid-classes: true; +$enable-print-styles: true; + +// Components: +// Define common padding and border radius sizes and more. + +$border-radius: 0.15rem; +$border-radius-lg: 0.125rem; +$border-radius-sm: 0.1rem; + +// Body: +// Settings for the `` element. + +$body-bg: #ffffff; + +// Typography: +// Font, line-height, and color for body text, headings, and more. + +$font-size-base: 1rem; + +$dropdown-link-hover-color: white; +$dropdown-link-hover-bg: #343a40; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/global.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/global.scss new file mode 100644 index 0000000..f939174 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/global.scss @@ -0,0 +1,239 @@ +@import 'bootstrap-variables'; +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; + +/* ============================================================== +Bootstrap tweaks +===============================================================*/ + +body, +h1, +h2, +h3, +h4 { + font-weight: 300; +} + +/* Increase contrast of links to get 100% on Lighthouse Accessability Audit. Override this color if you want to change the link color, or use a Bootswatch theme */ +a { + color: #533f03; + font-weight: bold; +} + +a:hover { + color: #533f03; +} + +/* override hover color for dropdown-item forced by bootstrap to all a:not([href]):not([tabindex]) elements in _reboot.scss */ +a:not([href]):not([tabindex]):hover.dropdown-item { + color: $dropdown-link-hover-color; +} + +/* override .dropdown-item.active background-color on hover */ +.dropdown-item.active:hover { + background-color: mix($dropdown-link-hover-bg, $dropdown-link-active-bg, 50%); +} + +a:hover { + /* make sure browsers use the pointer cursor for anchors, even with no href */ + cursor: pointer; +} + +.dropdown-item:hover { + color: $dropdown-link-hover-color; +} + +/* ========================================================================== +Browser Upgrade Prompt +========================================================================== */ +.browserupgrade { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; +} + +/* ========================================================================== +Generic styles +========================================================================== */ + +/* Error highlight on input fields */ +.ng-valid[required], +.ng-valid.required { + border-left: 5px solid green; +} + +.ng-invalid:not(form) { + border-left: 5px solid red; +} + +/* other generic styles */ + +.jh-card { + padding: 1.5%; + margin-top: 20px; + border: none; +} + +.error { + color: white; + background-color: red; +} + +.pad { + padding: 10px; +} + +.w-40 { + width: 40% !important; +} + +.w-60 { + width: 60% !important; +} + +.break { + white-space: normal; + word-break: break-all; +} + +.form-control { + background-color: #fff; +} + +.readonly { + background-color: #eee; + opacity: 1; +} + +.footer { + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.hand, +[jhisortby] { + cursor: pointer; +} + +/* ========================================================================== +Custom alerts for notification +========================================================================== */ +.alerts { + .alert { + text-overflow: ellipsis; + pre { + background: none; + border: none; + font: inherit; + color: inherit; + padding: 0; + margin: 0; + } + .popover pre { + font-size: 10px; + } + } + .jhi-toast { + position: fixed; + width: 100%; + &.left { + left: 5px; + } + &.right { + right: 5px; + } + &.top { + top: 55px; + } + &.bottom { + bottom: 55px; + } + } +} + +@media screen and (min-width: 480px) { + .alerts .jhi-toast { + width: 50%; + } +} + +/* ========================================================================== +entity list page css +========================================================================== */ + +.table-entities thead th .d-flex > * { + margin: auto 0; +} + +/* ========================================================================== +entity detail page css +========================================================================== */ +.row-md.jh-entity-details { + display: grid; + grid-template-columns: auto 1fr; + column-gap: 10px; + line-height: 1.5; +} + +@media screen and (min-width: 768px) { + .row-md.jh-entity-details > { + dt { + float: left; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + padding: 0.5em 0; + } + dd { + border-bottom: 1px solid #eee; + padding: 0.5em 0; + margin-left: 0; + } + } +} + +/* ========================================================================== +ui bootstrap tweaks +========================================================================== */ +.nav, +.pagination, +.carousel, +.panel-title a { + cursor: pointer; +} + +.thread-dump-modal-lock { + max-width: 450px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dropdown-menu { + padding-left: 0px; +} + +/* ========================================================================== +angular-cli removes postcss-rtl processed inline css, processed rules must be added here instead +========================================================================== */ +/* page-ribbon.component.scss */ +.ribbon { + left: -3.5em; + -moz-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + -o-transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); + transform: rotate(-45deg); +} + +/* navbar.component.scss */ +.navbar { + ul.navbar-nav { + .nav-item { + margin-left: 0.5em; + } + } +} +/* jhipster-needle-scss-add-main JHipster will add new css style */ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/vendor.scss b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/vendor.scss new file mode 100644 index 0000000..acf2df2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/content/scss/vendor.scss @@ -0,0 +1,12 @@ +/* after changing this file run 'npm run webapp:build' */ + +/*************************** +put Sass variables here: +eg $input-color: red; +****************************/ +// Override Bootstrap variables +@import 'bootstrap-variables'; +// Import Bootstrap source files from node_modules +@import 'bootstrap/scss/bootstrap'; + +/* jhipster-needle-scss-add-vendor JHipster will add new css style */ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/declarations.d.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/declarations.d.ts new file mode 100644 index 0000000..38352e9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/declarations.d.ts @@ -0,0 +1,2 @@ +declare const SERVER_API_URL: string; +declare const I18N_HASH: string; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/favicon.ico b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/favicon.ico new file mode 100644 index 0000000..4179874 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/favicon.ico differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/activate.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/activate.json new file mode 100644 index 0000000..0a537a8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/activate.json @@ -0,0 +1,9 @@ +{ + "activate": { + "title": "Aktivierung", + "messages": { + "success": "Ihr Benutzer wurde aktiviert. Bitte ", + "error": "Ihr Benutzer konnte nicht aktiviert werden. Bitte benutzen Sie die Registrierungsmaske, um sich zu registrieren." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/configuration.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/configuration.json new file mode 100644 index 0000000..838757d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/configuration.json @@ -0,0 +1,10 @@ +{ + "configuration": { + "title": "Konfiguration", + "filter": "Filter (nach Präfix)", + "table": { + "prefix": "Präfix", + "properties": "Eigenschaften" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/error.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/error.json new file mode 100644 index 0000000..beb5a12 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/error.json @@ -0,0 +1,14 @@ +{ + "error": { + "title": "Fehlerseite!", + "http": { + "400": "Ungültige Anfrage.", + "403": "Sie haben nicht die nötigen Berechtigungen um diese Seite anzuzeigen.", + "404": "Die Seite existiert nicht.", + "405": "Die verwendete Anfrage-Methode ist für diese URL nicht erlaubt.", + "500": "Interner Serverfehler." + }, + "concurrencyFailure": "Ein anderer Benutzer hat diese Daten zeitgleich mit Ihnen geändert. Ihre Änderungen wurden abgelehnt.", + "validation": "Validierungsfehler auf dem Server." + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/global.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/global.json new file mode 100644 index 0000000..c3f1b37 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/global.json @@ -0,0 +1,146 @@ +{ + "global": { + "title": "App", + "browsehappy": "Sie benutzen einen veralteten Browser. Bitte aktualisieren Sie Ihren Browser, um die Benutzerfreundlichkeit zu erhöhen.", + "menu": { + "home": "Startseite", + "jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)", + "entities": { + "main": "Entitäten", + "jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)" + }, + "account": { + "main": "Zugang", + "settings": "Einstellungen", + "password": "Passwort", + "sessions": "Sitzungen", + "login": "Anmelden", + "logout": "Abmelden", + "register": "Registrierung" + }, + "admin": { + "main": "Administration", + "userManagement": "Benutzerverwaltung", + "tracker": "Benutzer Aktivitäten", + "metrics": "Metriken", + "health": "Status", + "configuration": "Konfiguration", + "logs": "Protokoll", + "apidocs": "API", + "database": "Datenbank", + "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)" + }, + "language": "Sprache" + }, + "form": { + "username.label": "Benutzername", + "username.placeholder": "Ihr Benutzername", + "currentpassword.label": "Aktuelles Passwort", + "currentpassword.placeholder": "Aktuelles Passwort", + "newpassword.label": "Neues Passwort", + "newpassword.placeholder": "Neues Passwort", + "confirmpassword.label": "Neues Passwort bestätigen", + "confirmpassword.placeholder": "Bestätigen Sie Ihr neues Passwort", + "email.label": "Email Adresse", + "email.placeholder": "Ihre Email Adresse" + }, + "messages": { + "info": { + "authenticated": { + "prefix": "Wenn Sie sich ", + "link": "anmelden", + "suffix": " möchten, versuchen Sie es mit
- Administrator (Name=\"admin\" und Passwort=\"admin\")
- Benutzer (Name=\"user\" und Passwort=\"user\")." + }, + "register": { + "noaccount": "Sie haben noch keinen Zugang?", + "link": "Registrieren Sie sich" + } + }, + "error": { + "dontmatch": "Das bestätigte Passwort entspricht nicht dem neuen Passwort!" + }, + "validate": { + "newpassword": { + "required": "Ein neues Passwort wird benötigt.", + "minlength": "Das neue Passwort muss mindestens 4 Zeichen lang sein", + "maxlength": "Das neue Passwort darf nicht länger als 50 Zeichen sein", + "strength": "Passwortstärke:" + }, + "confirmpassword": { + "required": "Sie müssen das Passwort bestätigen.", + "minlength": "Das bestätigte Passwort muss mindestens 4 Zeichen lang sein", + "maxlength": "Das bestätigte Passwort darf nicht länger als 50 Zeichen sein" + }, + "email": { + "required": "Ihre Email Adresse wird benötigt.", + "invalid": "Ihre Email Adresse ist ungültig.", + "minlength": "Ihre Email Adresse muss mindestens 5 Zeichen lang sein", + "maxlength": "Ihre Email Adresse darf nicht länger als 50 Zeichen sein" + } + } + }, + "field": { + "id": "ID" + }, + "ribbon": { + "dev": "Entwicklung" + }, + "item-count": "Ergebnis {{first}} - {{second}} von {{total}} Elemente." + }, + "entity": { + "action": { + "addblob": "Blob hinzufügen", + "addimage": "Bild hinzufügen", + "back": "Zurück", + "cancel": "Abbrechen", + "delete": "Löschen", + "edit": "Bearbeiten", + "open": "Öffnen", + "save": "Speichern", + "view": "Details", + "show": "Zeige {{otherEntity}}" + }, + "detail": { + "field": "Feld", + "value": "Wert" + }, + "delete": { + "title": "Löschen bestätigen" + }, + "validation": { + "required": "Dieses Feld wird benötigt.", + "minlength": "Dieses Feld muss mind. {{min}} Zeichen lang sein.", + "maxlength": "Dieses Feld darf max. {{max}} Zeichen lang sein.", + "min": "Dieses Feld muss größer als {{min}} sein.", + "max": "Dieses Feld muss kleiner als {{max}} sein.", + "minbytes": "Dieses Feld sollte mehr als {{min}} bytes haben.", + "maxbytes": "Dieses Feld sollte nicht mehr als {{max}} bytes haben.", + "pattern": "Dieses Feld muss das Muster {{pattern}} erfüllen.", + "number": "Dieses Feld muss eine Zahl sein.", + "datetimelocal": "Dieses Feld muss eine Datums- und Zeitangabe enthalten." + }, + "filters": { + "set": "Folgende Filter sind gesetzt", + "clear": "Filter entfernen", + "clearAll": "Alle Filter entfernen" + } + }, + "error": { + "internalServerError": "Interner Serverfehler", + "server.not.reachable": "Server ist nicht erreichbar", + "url.not.found": "Nicht gefunden", + "NotNull": "Feld {{fieldName}} kann nicht leer sein!", + "Size": "Feld {{fieldName}} erfüllt nicht die minimalen/maximalen Voraussetzungen!", + "userexists": "Benutzername bereits vergeben!", + "emailexists": "Email wird bereits verwendet!", + "idexists": "Ein neuer {{entityName}} kann noch keine ID haben", + "idnull": "Ungültige ID", + "idinvalid": "Ungültige ID", + "idnotfound": "ID konnte nicht gefunden werden", + "file": { + "could.not.extract": "Datei konnte nicht extrahiert werden", + "not.image": "Es wird erwartet, dass die Datei ein Bild ist. Die Datei ist jedoch vom Typ \"{{ fileType }}\"" + } + }, + "footer": "Dies ist Ihre Fußzeile" +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/health.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/health.json new file mode 100644 index 0000000..7423cdd --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/health.json @@ -0,0 +1,31 @@ +{ + "health": { + "title": "Gesundheit Ihrer Anwendung", + "refresh.button": "Aktualisieren", + "stacktrace": "Stacktrace", + "details": { + "details": "Details", + "properties": "Properties", + "name": "Name", + "value": "Wert", + "error": "Error" + }, + "indicator": { + "diskSpace": "Festplattenspeicher", + "mail": "Email", + "livenessState": "Liveness state", + "readinessState": "Readiness state", + "ping": "Application", + "db": "Datenbank" + }, + "table": { + "service": "Dienst Name", + "status": "Status" + }, + "status": { + "UNKNOWN": "UNKNOWN", + "UP": "UP", + "DOWN": "DOWN" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/home.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/home.json new file mode 100644 index 0000000..2e259d7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/home.json @@ -0,0 +1,19 @@ +{ + "home": { + "title": "Willkommen, Java Hipster!", + "subtitle": "Dies ist Ihre Hauptseite", + "logged": { + "message": "Sie sind als Benutzer \"{{username}}\" angemeldet." + }, + "question": "Wenn Sie Fragen zu JHipster haben:", + "link": { + "homepage": "JHipster Hauptseite", + "stackoverflow": "JHipster auf Stack Overflow", + "bugtracker": "JHipster Fehlereinträge", + "chat": "JHipster Chat", + "follow": "kontaktieren Sie uns mit @jhipster auf Twitter" + }, + "like": "Wenn Sie JHipster mögen, vergessen Sie nicht uns einen Stern zu geben auf", + "github": "GitHub" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/login.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/login.json new file mode 100644 index 0000000..1a3e9e4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/login.json @@ -0,0 +1,19 @@ +{ + "login": { + "title": "Anmeldung", + "form": { + "password": "Passwort", + "password.placeholder": "Ihr Passwort", + "rememberme": "Automatische Anmeldung", + "button": "Anmelden" + }, + "messages": { + "error": { + "authentication": "Anmeldung fehlgeschlagen! Überprüfen Sie bitte Ihre Angaben und versuchen Sie es erneut." + } + }, + "password": { + "forgot": "Sie haben Ihr Passwort vergessen?" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/logs.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/logs.json new file mode 100644 index 0000000..f968735 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/logs.json @@ -0,0 +1,11 @@ +{ + "logs": { + "title": "Protokolle", + "nbloggers": "Es existieren {{ total }} Logger.", + "filter": "Filter", + "table": { + "name": "Name", + "level": "Stufe" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/metrics.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/metrics.json new file mode 100644 index 0000000..a5cf0dc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/metrics.json @@ -0,0 +1,94 @@ +{ + "metrics": { + "title": "Anwendungs-Metriken", + "refresh.button": "Aktualisieren", + "updating": "Aktualisierung...", + "jvm": { + "title": "JVM Metriken", + "memory": { + "title": "Hauptspeicher", + "total": "Gesamter Hauptspeicher", + "heap": "Heap Speicher", + "nonheap": "Non-Heap Speicher" + }, + "threads": { + "title": "Threads", + "all": "Alle", + "runnable": "Runnable", + "timedwaiting": "Wartezeit", + "waiting": "Wartend", + "blocked": "Geblockt", + "dump": { + "title": "Threads dump", + "id": "Id: ", + "blockedtime": "Geblockte Zeit", + "blockedcount": "Geblockte Zahl", + "waitedtime": "Gewartete Zeit", + "waitedcount": "Gewartete Anzahl", + "lockname": "Lock-Name", + "stacktrace": "Stacktrace", + "show": "Zeigen", + "hide": "Verstecken" + } + }, + "gc": { + "title": "Speicherbereinigung (GC)", + "marksweepcount": "Durchlaufmarkierungs (Mark Sweep) Anzahl", + "marksweeptime": "Durchlaufmarkierungs (Mark Sweep) Zeit", + "scavengecount": "Bereinigungslauf (Scavenge) Anzahl", + "scavengetime": "Bereinigungslauf (Scavenge) Zeit" + }, + "http": { + "title": "HTTP Anfragen (Ereignisse pro Sekunde)", + "active": "Aktive Anfragen:", + "total": "Alle Anfragen:", + "table": { + "code": "Codierung", + "count": "Anzahl", + "mean": "Durchschnittswert", + "average": "Mittelwert", + "max": "Max" + }, + "code": { + "ok": "Ok", + "notfound": "Nicht gefunden", + "servererror": "Server Fehler" + } + } + }, + "servicesstats": { + "title": "Service Statistiken (Zeit in Millisekunden)", + "table": { + "name": "Service Name", + "count": "Anzahl", + "mean": "Durchschnitt", + "min": "Minimum", + "max": "Maximum", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + }, + "cache": { + "title": "Cache Statistiken", + "cachename": "Cache Name", + "hits": "Treffer", + "misses": "Keine Treffer", + "evictions": "Anzahl entfernter Objekte" + }, + "datasource": { + "usage": "Usage", + "title": "Datenquelle (Zeit in Millisekunden)", + "name": "Pool-Auslastung", + "count": "Anzahl", + "mean": "Mittel", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/password.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/password.json new file mode 100644 index 0000000..e5d83e2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/password.json @@ -0,0 +1,12 @@ +{ + "password": { + "title": "Passwort für [{{username}}]", + "form": { + "button": "Speichern" + }, + "messages": { + "error": "Es ist ein Fehler aufgetreten! Das Passwort konnte nicht geändert werden.", + "success": "Passwort wurde geändert!" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/register.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/register.json new file mode 100644 index 0000000..d7ce8a4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/register.json @@ -0,0 +1,24 @@ +{ + "register": { + "title": "Registrierung", + "form": { + "button": "Registrieren" + }, + "messages": { + "validate": { + "login": { + "required": "Ihr Benutzername wird benötigt.", + "minlength": "Ihr Benutzername muss mindestens ein Zeichen lang sein.", + "maxlength": "Ihr Benutzername darf nicht länger als 50 Zeichen sein.", + "pattern": "Ihr Benutzername ist ungültig." + } + }, + "success": "Registrierung gespeichert! Bitte überprüfen Sie Ihre E-Mails für die Bestätigung.", + "error": { + "fail": "Registrierung fehlgeschlagen! Bitte versuchen Sie es später nochmal.", + "userexists": "Benutzername bereits vergeben! Bitte wählen Sie einen anderen aus.", + "emailexists": "E-Mail-Adresse wird bereits verwendet! Bitte wählen Sie eine andere aus." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/reset.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/reset.json new file mode 100644 index 0000000..68c6949 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/reset.json @@ -0,0 +1,26 @@ +{ + "reset": { + "request": { + "title": "Passwort zurücksetzen", + "form": { + "button": "Passwort zurücksetzen" + }, + "messages": { + "info": "Geben Sie die Email Adresse ein, welche Sie bei der Registrierung verwendet haben.", + "success": "Eine Email mit weiteren Instruktionen für das Zurücksetzen des Passworts wurde gesendet." + } + }, + "finish": { + "title": "Passwort zurücksetzen", + "form": { + "button": "Neues Passwort setzen" + }, + "messages": { + "info": "Wählen Sie ein neues Passwort", + "success": "Ihr Passwort wurde zurückgesetzt. Bitte ", + "keymissing": "Der Schlüssel zum Zurücksetzen fehlt.", + "error": "Ihr Passwort konnte nicht zurückgesetzt werden. Zur Erinnerung, Ihre Anfrage ist nur 24 Stunden gültig." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/sessions.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/sessions.json new file mode 100644 index 0000000..34c8a89 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/sessions.json @@ -0,0 +1,15 @@ +{ + "sessions": { + "title": "Aktive Sitzungen für [{{username}}]", + "table": { + "ipaddress": "IP Adresse", + "useragent": "User Agent", + "date": "Datum", + "button": "Schließen" + }, + "messages": { + "success": "Sitzung wurde geschlossen!", + "error": "Es ist ein Fehler aufgetreten! Die Sitzung konnte nicht geschlossen werden." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/settings.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/settings.json new file mode 100644 index 0000000..28cd2b6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/settings.json @@ -0,0 +1,32 @@ +{ + "settings": { + "title": "Einstellungen für Benutzer [{{username}}]", + "form": { + "firstname": "Vorname", + "firstname.placeholder": "Ihr Vorname", + "lastname": "Nachname", + "lastname.placeholder": "Ihr Nachname", + "language": "Sprache", + "button": "Speichern" + }, + "messages": { + "error": { + "fail": "Es ist ein Fehler aufgetreten! Die Einstellungen konnten nicht gespeichert werden.", + "emailexists": "Email wird bereits verwendet! Bitte wählen Sie eine andere aus." + }, + "success": "Einstellungen wurden gespeichert!", + "validate": { + "firstname": { + "required": "Ihr Vorname wird benötigt.", + "minlength": "Ihr Vorname muss mindestens 1 Zeichen lang sein", + "maxlength": "Ihr Vorname darf nicht länger als 50 Zeichen sein" + }, + "lastname": { + "required": "Ihr Nachname wird benötigt.", + "minlength": "Ihr Nachname muss mindestens 1 Zeichen lang sein", + "maxlength": "Ihr Nachname darf nicht länger als 50 Zeichen sein" + } + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/user-management.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/user-management.json new file mode 100644 index 0000000..8bd97f7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/de/user-management.json @@ -0,0 +1,31 @@ +{ + "userManagement": { + "home": { + "title": "Benutzer", + "refreshListLabel": "Refresh list", + "createLabel": "Neuen Benutzer erstellen", + "createOrEditLabel": "Benutzer erstellen oder bearbeiten" + }, + "created": "Ein neuer Benutzer wurde mit ID {{ param }} erstellt", + "updated": "Ein Benutzer mit ID {{ param }} wurde geändert", + "deleted": "Ein Benutzer mit ID {{ param }} wurde gelöscht", + "delete": { + "question": "Sind Sie sicher, dass Sie den Benutzer {{ login }} löschen möchten?" + }, + "detail": { + "title": "Benutzer" + }, + "login": "Login", + "firstName": "Vorname", + "lastName": "Nachname", + "email": "Email", + "activated": "Aktiv", + "deactivated": "Deaktiviert", + "profiles": "Profile", + "langKey": "Sprache", + "createdBy": "Erstellt von", + "createdDate": "Erstellt am", + "lastModifiedBy": "Bearbeitet von", + "lastModifiedDate": "Zuletzt bearbeitet" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/activate.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/activate.json new file mode 100644 index 0000000..058bd49 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/activate.json @@ -0,0 +1,9 @@ +{ + "activate": { + "title": "Activation", + "messages": { + "success": "Your user account has been activated. Please ", + "error": "Your user could not be activated. Please use the registration form to sign up." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/configuration.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/configuration.json new file mode 100644 index 0000000..09c9a1a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/configuration.json @@ -0,0 +1,10 @@ +{ + "configuration": { + "title": "Configuration", + "filter": "Filter (by prefix)", + "table": { + "prefix": "Prefix", + "properties": "Properties" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/error.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/error.json new file mode 100644 index 0000000..d257563 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/error.json @@ -0,0 +1,14 @@ +{ + "error": { + "title": "Error page!", + "http": { + "400": "Bad request.", + "403": "You are not authorized to access this page.", + "404": "The page does not exist.", + "405": "The HTTP verb you used is not supported for this URL.", + "500": "Internal server error." + }, + "concurrencyFailure": "Another user modified this data at the same time as you. Your changes were rejected.", + "validation": "Validation error on the server." + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/global.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/global.json new file mode 100644 index 0000000..23b25f1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/global.json @@ -0,0 +1,147 @@ +{ + "global": { + "title": "App", + "browsehappy": "You are using an outdated browser. Please upgrade your browser to improve your experience.", + "menu": { + "home": "Home", + "jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)", + "entities": { + "main": "Entities", + "jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)" + }, + "account": { + "main": "Account", + "settings": "Settings", + "password": "Password", + "sessions": "Sessions", + "login": "Sign in", + "logout": "Sign out", + "register": "Register" + }, + "admin": { + "main": "Administration", + "userManagement": "User management", + "tracker": "User tracker", + "metrics": "Metrics", + "health": "Health", + "configuration": "Configuration", + "logs": "Logs", + "apidocs": "API", + "database": "Database", + "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)" + }, + "language": "Language" + }, + "form": { + "username.label": "Username", + "username.placeholder": "Your username", + "currentpassword.label": "Current password", + "currentpassword.placeholder": "Current password", + "newpassword.label": "New password", + "newpassword.placeholder": "New password", + "confirmpassword.label": "New password confirmation", + "confirmpassword.placeholder": "Confirm the new password", + "email.label": "Email", + "email.placeholder": "Your email" + }, + "messages": { + "info": { + "authenticated": { + "prefix": "If you want to ", + "link": "sign in", + "suffix": ", you can try the default accounts:
- Administrator (login=\"admin\" and password=\"admin\")
- User (login=\"user\" and password=\"user\")." + }, + "register": { + "noaccount": "You don't have an account yet?", + "link": "Register a new account" + } + }, + "error": { + "dontmatch": "The password and its confirmation do not match!" + }, + "validate": { + "newpassword": { + "required": "Your password is required.", + "minlength": "Your password is required to be at least 4 characters.", + "maxlength": "Your password cannot be longer than 50 characters.", + "strength": "Password strength:" + }, + "confirmpassword": { + "required": "Your confirmation password is required.", + "minlength": "Your confirmation password is required to be at least 4 characters.", + "maxlength": "Your confirmation password cannot be longer than 50 characters." + }, + "email": { + "required": "Your email is required.", + "invalid": "Your email is invalid.", + "minlength": "Your email is required to be at least 5 characters.", + "maxlength": "Your email cannot be longer than 50 characters." + } + } + }, + "field": { + "id": "ID" + }, + "ribbon": { + "dev": "Development" + }, + "item-count": "Showing {{first}} - {{second}} of {{total}} items." + }, + "entity": { + "action": { + "addblob": "Add blob", + "addimage": "Add image", + "back": "Back", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "open": "Open", + "save": "Save", + "view": "View", + "show": "Show {{otherEntity}}" + }, + "detail": { + "field": "Field", + "value": "Value" + }, + "delete": { + "title": "Confirm delete operation" + }, + "validation": { + "required": "This field is required.", + "minlength": "This field is required to be at least {{ min }} characters.", + "maxlength": "This field cannot be longer than {{ max }} characters.", + "min": "This field should be at least {{ min }}.", + "max": "This field cannot be more than {{ max }}.", + "minbytes": "This field should be at least {{ min }} bytes.", + "maxbytes": "This field cannot be more than {{ max }} bytes.", + "pattern": "This field should follow pattern for {{ pattern }}.", + "number": "This field should be a number.", + "datetimelocal": "This field should be a date and time.", + "patternLogin": "This field can only contain letters, digits and e-mail addresses." + }, + "filters": { + "set": "Following filters are set", + "clear": "Clear filter", + "clearAll": "Clear all filters" + } + }, + "error": { + "internalServerError": "Internal server error", + "server.not.reachable": "Server not reachable", + "url.not.found": "Not found", + "NotNull": "Field {{ fieldName }} cannot be empty!", + "Size": "Field {{ fieldName }} does not meet min/max size requirements!", + "userexists": "Login name already used!", + "emailexists": "Email is already in use!", + "idexists": "A new {{ entityName }} cannot already have an ID", + "idnull": "Invalid ID", + "idinvalid": "Invalid ID", + "idnotfound": "ID cannot be found", + "file": { + "could.not.extract": "Could not extract file", + "not.image": "File was expected to be an image but was found to be \"{{ fileType }}\"" + } + }, + "footer": "This is your footer" +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/health.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/health.json new file mode 100644 index 0000000..cbc45ba --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/health.json @@ -0,0 +1,32 @@ +{ + "health": { + "title": "Health Checks", + "refresh.button": "Refresh", + "stacktrace": "Stacktrace", + "details": { + "details": "Details", + "properties": "Properties", + "name": "Name", + "value": "Value", + "error": "Error" + }, + "indicator": { + "diskSpace": "Disk space", + "mail": "Email", + "livenessState": "Liveness state", + "readinessState": "Readiness state", + "ping": "Application", + "db": "Database" + }, + "table": { + "service": "Service name", + "status": "Status" + }, + "status": { + "UNKNOWN": "UNKNOWN", + "UP": "UP", + "OUT_OF_SERVICE": "OUT_OF_SERVICE", + "DOWN": "DOWN" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/home.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/home.json new file mode 100644 index 0000000..d9fc499 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/home.json @@ -0,0 +1,19 @@ +{ + "home": { + "title": "Welcome, Java Hipster!", + "subtitle": "This is your homepage", + "logged": { + "message": "You are logged in as user \"{{username}}\"." + }, + "question": "If you have any question on JHipster:", + "link": { + "homepage": "JHipster homepage", + "stackoverflow": "JHipster on Stack Overflow", + "bugtracker": "JHipster bug tracker", + "chat": "JHipster public chat room", + "follow": "follow @jhipster on Twitter" + }, + "like": "If you like JHipster, don't forget to give us a star on", + "github": "GitHub" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/login.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/login.json new file mode 100644 index 0000000..4667958 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/login.json @@ -0,0 +1,19 @@ +{ + "login": { + "title": "Sign in", + "form": { + "password": "Password", + "password.placeholder": "Your password", + "rememberme": "Remember me", + "button": "Sign in" + }, + "messages": { + "error": { + "authentication": "Failed to sign in! Please check your credentials and try again." + } + }, + "password": { + "forgot": "Did you forget your password?" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/logs.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/logs.json new file mode 100644 index 0000000..8ee0ba5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/logs.json @@ -0,0 +1,11 @@ +{ + "logs": { + "title": "Logs", + "nbloggers": "There are {{ total }} loggers.", + "filter": "Filter", + "table": { + "name": "Name", + "level": "Level" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/metrics.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/metrics.json new file mode 100644 index 0000000..c514dee --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/metrics.json @@ -0,0 +1,102 @@ +{ + "metrics": { + "title": "Application Metrics", + "refresh.button": "Refresh", + "updating": "Updating...", + "jvm": { + "title": "JVM Metrics", + "memory": { + "title": "Memory", + "total": "Total Memory", + "heap": "Heap Memory", + "nonheap": "Non-Heap Memory" + }, + "threads": { + "title": "Threads", + "all": "All", + "runnable": "Runnable", + "timedwaiting": "Timed waiting", + "waiting": "Waiting", + "blocked": "Blocked", + "dump": { + "title": "Threads dump", + "id": "Id: ", + "blockedtime": "Blocked Time", + "blockedcount": "Blocked Count", + "waitedtime": "Waited Time", + "waitedcount": "Waited Count", + "lockname": "Lock name", + "stacktrace": "Stacktrace", + "show": "Show Stacktrace", + "hide": "Hide Stacktrace" + } + }, + "gc": { + "title": "Garbage collections", + "marksweepcount": "Mark Sweep count", + "marksweeptime": "Mark Sweep time", + "scavengecount": "Scavenge count", + "scavengetime": "Scavenge time" + }, + "http": { + "title": "HTTP requests (time in millisecond)", + "active": "Active requests:", + "total": "Total requests:", + "table": { + "code": "Code", + "count": "Count", + "mean": "Mean", + "average": "Average", + "max": "Max" + }, + "code": { + "ok": "Ok", + "notfound": "Not found", + "servererror": "Server Error" + } + } + }, + "servicesstats": { + "title": "Services statistics (time in millisecond)", + "table": { + "name": "Service name", + "count": "Count", + "mean": "Mean", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + }, + "cache": { + "title": "Cache statistics", + "cachename": "Cache name", + "hits": "Cache Hits", + "misses": "Cache Misses", + "gets": "Cache Gets", + "puts": "Cache Puts", + "removals": "Cache Removals", + "evictions": "Cache Evictions", + "hitPercent": "Cache Hit %", + "missPercent": "Cache Miss %", + "averageGetTime": "Average get time (µs)", + "averagePutTime": "Average put time (µs)", + "averageRemoveTime": "Average remove time (µs)" + }, + "datasource": { + "usage": "Connection Pool Usage", + "title": "DataSource statistics (time in millisecond)", + "name": "Pool usage", + "count": "Count", + "mean": "Mean", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/password.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/password.json new file mode 100644 index 0000000..fc9b6c9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/password.json @@ -0,0 +1,12 @@ +{ + "password": { + "title": "Password for [{{username}}]", + "form": { + "button": "Save" + }, + "messages": { + "error": "An error has occurred! The password could not be changed.", + "success": "Password changed!" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/register.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/register.json new file mode 100644 index 0000000..1a0aeec --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/register.json @@ -0,0 +1,24 @@ +{ + "register": { + "title": "Registration", + "form": { + "button": "Register" + }, + "messages": { + "validate": { + "login": { + "required": "Your username is required.", + "minlength": "Your username is required to be at least 1 character.", + "maxlength": "Your username cannot be longer than 50 characters.", + "pattern": "Your username is invalid." + } + }, + "success": "Registration saved! Please check your email for confirmation.", + "error": { + "fail": "Registration failed! Please try again later.", + "userexists": "Login name already registered! Please choose another one.", + "emailexists": "Email is already in use! Please choose another one." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/reset.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/reset.json new file mode 100644 index 0000000..062d3e7 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/reset.json @@ -0,0 +1,26 @@ +{ + "reset": { + "request": { + "title": "Reset your password", + "form": { + "button": "Reset password" + }, + "messages": { + "info": "Enter the email address you used to register", + "success": "Check your email for details on how to reset your password." + } + }, + "finish": { + "title": "Reset password", + "form": { + "button": "Validate new password" + }, + "messages": { + "info": "Choose a new password", + "success": "Your password has been reset. Please ", + "keymissing": "The reset key is missing.", + "error": "Your password couldn't be reset. Remember a password request is only valid for 24 hours." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/sessions.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/sessions.json new file mode 100644 index 0000000..38bf44f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/sessions.json @@ -0,0 +1,15 @@ +{ + "sessions": { + "title": "Active sessions for [{{username}}]", + "table": { + "ipaddress": "IP address", + "useragent": "User Agent", + "date": "Date", + "button": "Invalidate" + }, + "messages": { + "success": "Session invalidated!", + "error": "An error has occurred! The session could not be invalidated." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/settings.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/settings.json new file mode 100644 index 0000000..508c037 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/settings.json @@ -0,0 +1,32 @@ +{ + "settings": { + "title": "User settings for [{{username}}]", + "form": { + "firstname": "First Name", + "firstname.placeholder": "Your first name", + "lastname": "Last Name", + "lastname.placeholder": "Your last name", + "language": "Language", + "button": "Save" + }, + "messages": { + "error": { + "fail": "An error has occurred! Settings could not be saved.", + "emailexists": "Email is already in use! Please choose another one." + }, + "success": "Settings saved!", + "validate": { + "firstname": { + "required": "Your first name is required.", + "minlength": "Your first name is required to be at least 1 character", + "maxlength": "Your first name cannot be longer than 50 characters" + }, + "lastname": { + "required": "Your last name is required.", + "minlength": "Your last name is required to be at least 1 character", + "maxlength": "Your last name cannot be longer than 50 characters" + } + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/user-management.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/user-management.json new file mode 100644 index 0000000..52b56a1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/en/user-management.json @@ -0,0 +1,31 @@ +{ + "userManagement": { + "home": { + "title": "Users", + "refreshListLabel": "Refresh list", + "createLabel": "Create a new user", + "createOrEditLabel": "Create or edit a user" + }, + "created": "A new user is created with identifier {{ param }}", + "updated": "A user is updated with identifier {{ param }}", + "deleted": "A user is deleted with identifier {{ param }}", + "delete": { + "question": "Are you sure you want to delete user {{ login }}?" + }, + "detail": { + "title": "User" + }, + "login": "Login", + "firstName": "First name", + "lastName": "Last name", + "email": "Email", + "activated": "Activated", + "deactivated": "Deactivated", + "profiles": "Profiles", + "langKey": "Language", + "createdBy": "Created by", + "createdDate": "Created date", + "lastModifiedBy": "Modified by", + "lastModifiedDate": "Modified date" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/activate.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/activate.json new file mode 100644 index 0000000..7b29d28 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/activate.json @@ -0,0 +1,9 @@ +{ + "activate": { + "title": "Activation", + "messages": { + "success": "Votre compte utilisateur a été activé. Merci de vous ", + "error": "Votre compte utilisateur n'a pas pu être activé. Utilisez le formulaire d'enregistrement pour en créer un nouveau." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/configuration.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/configuration.json new file mode 100644 index 0000000..a9a014a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/configuration.json @@ -0,0 +1,10 @@ +{ + "configuration": { + "title": "Configuration", + "filter": "Filtrer (par préfixe)", + "table": { + "prefix": "Préfixe", + "properties": "Propriétés" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/error.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/error.json new file mode 100644 index 0000000..71622e5 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/error.json @@ -0,0 +1,14 @@ +{ + "error": { + "title": "Page d'erreur !", + "http": { + "400": "Mauvaise requête.", + "403": "Vous n'avez pas les droits pour accéder à cette page.", + "404": "La page n'existe pas.", + "405": "Le verbe HTTP que vous avez utilisé n'est pas reconnu par cet URL.", + "500": "Erreur interne du serveur." + }, + "concurrencyFailure": "Un autre utilisateur a modifié ces données en même temps que vous. Vos changements n'ont pas été sauvegardés.", + "validation": "Erreur de validation côté serveur." + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/global.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/global.json new file mode 100644 index 0000000..cc12dd9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/global.json @@ -0,0 +1,147 @@ +{ + "global": { + "title": "App", + "browsehappy": "Vous utilisez un ancien navigateur. Veuillez mettre à jour votre navigateur pour une meilleure expérience.", + "menu": { + "home": "Accueil", + "jhipster-needle-menu-add-element": "JHipster will add additional menu entries here (do not translate!)", + "entities": { + "main": "Entités", + "jhipster-needle-menu-add-entry": "JHipster will add additional entities here (do not translate!)" + }, + "account": { + "main": "Compte", + "settings": "Profil", + "password": "Mot de passe", + "sessions": "Sessions", + "login": "S'authentifier", + "logout": "Déconnexion", + "register": "Créer un compte" + }, + "admin": { + "main": "Administration", + "userManagement": "Gestion des utilisateurs", + "tracker": "Suivi des utilisateurs", + "metrics": "Métriques", + "health": "Diagnostics", + "configuration": "Configuration", + "logs": "Logs", + "apidocs": "API", + "database": "Base de données", + "jhipster-needle-menu-add-admin-element": "JHipster will add additional menu entries here (do not translate!)" + }, + "language": "Langue" + }, + "form": { + "username.label": "Nom d'utilisateur", + "username.placeholder": "Votre nom d'utilisateur", + "currentpassword.label": "Mot de passe actuel", + "currentpassword.placeholder": "Mot de passe actuel", + "newpassword.label": "Nouveau mot de passe", + "newpassword.placeholder": "Nouveau mot de passe", + "confirmpassword.label": "Confirmation du nouveau mot de passe", + "confirmpassword.placeholder": "Confirmation du nouveau mot de passe", + "email.label": "Email", + "email.placeholder": "Votre email" + }, + "messages": { + "info": { + "authenticated": { + "prefix": "Si vous voulez vous ", + "link": "connecter", + "suffix": ", vous pouvez utiliser les comptes par défaut :
- Administrateur (nom d'utilisateur=\"admin\" et mot de passe =\"admin\")
- Utilisateur (nom d'utilisateur=\"user\" et mot de passe =\"user\")." + }, + "register": { + "noaccount": "Vous n'avez pas encore de compte ?", + "link": "Créer un compte" + } + }, + "error": { + "dontmatch": "Le nouveau mot de passe et sa confirmation ne sont pas égaux !" + }, + "validate": { + "newpassword": { + "required": "Votre mot de passe est requis.", + "minlength": "Votre mot de passe doit comporter au moins 4 caractères.", + "maxlength": "Votre mot de passe ne doit pas comporter plus de 50 caractères.", + "strength": "Robustesse du mot de passe :" + }, + "confirmpassword": { + "required": "Votre confirmation du mot de passe est requise.", + "minlength": "Votre confirmation du mot de passe doit comporter au moins 4 caractères.", + "maxlength": "Votre confirmation du mot de passe ne doit pas comporter plus de 50 caractères." + }, + "email": { + "required": "Votre email est requis.", + "minlength": "Votre email doit comporter au moins 5 caractères.", + "maxlength": "Votre email ne doit pas comporter plus de 50 caractères.", + "invalid": "Votre email n'est pas valide." + } + } + }, + "field": { + "id": "ID" + }, + "ribbon": { + "dev": "Développement" + }, + "item-count": "Affichage {{first}} - {{second}} de {{total}} items." + }, + "entity": { + "action": { + "addblob": "Ajouter blob", + "addimage": "Ajouter image", + "back": "Retour", + "cancel": "Annuler", + "edit": "Editer", + "delete": "Supprimer", + "open": "Ouvrir", + "save": "Sauvegarder", + "view": "Voir", + "show": "Show {{otherEntity}}" + }, + "detail": { + "field": "Champs", + "value": "Valeur" + }, + "delete": { + "title": "Confirmation de suppression" + }, + "validation": { + "required": "Ce champ est obligatoire.", + "minlength": "Ce champ doit faire au minimum {{min}} caractères.", + "maxlength": "Ce champ doit faire moins de {{max}} caractères.", + "min": "Ce champ doit être supérieur à {{min}}.", + "max": "Ce champ doit être inférieur à {{max}}.", + "minbytes": "Ce champ doit être supérieur à {{min}} bytes.", + "maxbytes": "Ce champ doit être inférieur à {{max}} bytes.", + "pattern": "Ce champ doit suivre l'expression régulière {{pattern}}.", + "number": "Ce champ doit être un nombre.", + "datetimelocal": "Ce champ doit être une date et une heure.", + "patternLogin": "Ce champ ne peut contenir que des lettres, des chiffres ou des adresses e-mail." + }, + "filters": { + "set": "Following filters are set", + "clear": "Clear filter", + "clearAll": "Clear all filters" + } + }, + "error": { + "internalServerError": "Erreur interne du serveur", + "server.not.reachable": "Serveur inaccessible", + "url.not.found": "Non trouvé", + "NotNull": "Le champ {{fieldName}} ne peut pas être vide !", + "Size": "Le champ {{fieldName}} ne respecte pas les critères minimum et maximum !", + "userexists": "Login déjà utilisé !", + "emailexists": "Email déjà utilisé !", + "idexists": "Une nouvelle entité {{entityName}} ne peut pas avoir d'identifiant !", + "idnull": "Identifiant invalide", + "idinvalid": "Invalid Id", + "idnotfound": "ID cannot be found", + "file": { + "could.not.extract": "Impossible d'extraire le fichier", + "not.image": "Le fichier doit être une image et non du type \"{{ fileType }}\"" + } + }, + "footer": "Ceci est votre pied de page" +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/health.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/health.json new file mode 100644 index 0000000..03f0bfb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/health.json @@ -0,0 +1,31 @@ +{ + "health": { + "title": "Diagnostics", + "refresh.button": "Rafraîchir", + "stacktrace": "Stacktrace", + "details": { + "details": "Détails", + "properties": "Propriétés", + "name": "Nom", + "value": "Valeur", + "error": "Erreur" + }, + "indicator": { + "diskSpace": "Espace disque", + "mail": "Email", + "livenessState": "Liveness state", + "readinessState": "Readiness state", + "ping": "Application", + "db": "Base de données" + }, + "table": { + "service": "Nom du service", + "status": "État" + }, + "status": { + "UNKNOWN": "INCONNU", + "UP": "DISPONIBLE", + "DOWN": "HORS SERVICE" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/home.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/home.json new file mode 100644 index 0000000..44dc4b9 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/home.json @@ -0,0 +1,19 @@ +{ + "home": { + "title": "Bienvenue, Java Hipster !", + "subtitle": "Ceci est votre page d'accueil", + "logged": { + "message": "Vous êtes connecté en tant que \"{{username}}\"." + }, + "question": "Si vous avez des questions à propos de JHipster :", + "link": { + "homepage": "Page d'accueil de JHipster", + "stackoverflow": "JHipster sur Stack Overflow", + "bugtracker": "JHipster bug tracker", + "chat": "JHipster public chat room", + "follow": "Suivez @jhipster sur Twitter" + }, + "like": "Si vous aimez JHipster, donnez nous une étoile sur", + "github": "GitHub " + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/login.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/login.json new file mode 100644 index 0000000..f5aa2af --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/login.json @@ -0,0 +1,19 @@ +{ + "login": { + "title": "Authentification", + "form": { + "password": "Mot de passe", + "password.placeholder": "Votre mot de passe", + "rememberme": "Garder la session ouverte", + "button": "Connexion" + }, + "messages": { + "error": { + "authentication": "Erreur d'authentification ! Veuillez vérifier vos identifiants de connexion." + } + }, + "password": { + "forgot": "Avez-vous oublié votre mot de passe ?" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/logs.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/logs.json new file mode 100644 index 0000000..e0acb27 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/logs.json @@ -0,0 +1,11 @@ +{ + "logs": { + "title": "Logs", + "nbloggers": "Total de {{ total }} \"loggers\".", + "filter": "Filtrer", + "table": { + "name": "Nom", + "level": "Niveau" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/metrics.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/metrics.json new file mode 100644 index 0000000..cbd5c4a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/metrics.json @@ -0,0 +1,102 @@ +{ + "metrics": { + "title": "Métriques de l'application", + "refresh.button": "Rafraîchir", + "updating": "Mise à jour...", + "jvm": { + "title": "Métriques de la JVM", + "memory": { + "title": "Mémoire", + "total": "Mémoire totale", + "heap": "Mémoire \"Heap\"", + "nonheap": "Mémoire \"Non-Heap\"" + }, + "threads": { + "title": "Threads", + "all": "Tous", + "runnable": "Exécutable", + "timedwaiting": "Temps d'attente", + "waiting": "En attente", + "blocked": "Bloqué", + "dump": { + "title": "Threads dump", + "id": "Id :", + "blockedtime": "Temps bloqué", + "blockedcount": "Nb fois bloqué", + "waitedtime": "Temps d'attente", + "waitedcount": "Nb fois en attente", + "lockname": "Nom du process bloquant", + "stacktrace": "Stacktrace", + "show": "Afficher", + "hide": "Cacher" + } + }, + "gc": { + "title": "Garbage collections", + "marksweepcount": "Total de \"Mark Sweep\"", + "marksweeptime": "Temps \"Mark Sweep\"", + "scavengecount": "Total \"Scavenge\"", + "scavengetime": "Temps \"Scavenge\"" + }, + "http": { + "title": "Requêtes HTTP (temps en millisecondes)", + "active": "Requêtes actives :", + "total": "Requêtes totales :", + "table": { + "code": "Code", + "count": "Total", + "mean": "Médian", + "average": "Moyenne", + "max": "Max" + }, + "code": { + "ok": "Ok", + "notfound": "Non trouvé", + "servererror": "Erreur serveur" + } + } + }, + "servicesstats": { + "title": "Statistiques des services (temps en millisecondes)", + "table": { + "name": "Nom du server", + "count": "Total", + "mean": "Médian", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + }, + "cache": { + "title": "Statistiques de cache", + "cachename": "Nom du cache", + "hits": "Données existantes", + "misses": "Données non existantes", + "gets": "Lectures", + "puts": "Écritures", + "removals": "Suppressions", + "evictions": "Évictions", + "hitPercent": "% données existantes", + "missPercent": "% données non existantes", + "averageGetTime": "Temps de lecture moyen (µs)", + "averagePutTime": "Temps d'écriture moyen (µs)", + "averageRemoveTime": "Temps de suppression moyen (µs)" + }, + "datasource": { + "usage": "Usage du Pool de connexion", + "title": "Statistiques du pool de connexions (temps en millisecondes)", + "name": "Utilisation du pool", + "count": "Total", + "mean": "Médian", + "min": "Min", + "max": "Max", + "p50": "p50", + "p75": "p75", + "p95": "p95", + "p99": "p99" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/password.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/password.json new file mode 100644 index 0000000..1c5856b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/password.json @@ -0,0 +1,12 @@ +{ + "password": { + "title": "Changer le mot de passe pour [{{username}}]", + "form": { + "button": "Sauvegarder" + }, + "messages": { + "error": "Une erreur est survenue ! Le mot de passe n'a pas pu être modifié.", + "success": "Le mot de passe a été modifié !" + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/register.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/register.json new file mode 100644 index 0000000..08eb09d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/register.json @@ -0,0 +1,24 @@ +{ + "register": { + "title": "Création de compte utilisateur", + "form": { + "button": "Enregistrement" + }, + "messages": { + "validate": { + "login": { + "required": "Votre nom d'utilisateur est obligatoire.", + "minlength": "Votre nom d'utilisateur doit contenir plus d'un caractère.", + "maxlength": "Votre nom d'utilisateur ne peut pas contenir plus de 50 caractères.", + "pattern": "Votre nom d'utilisateur est invalide." + } + }, + "success": "Compte enregistré ! Merci de vérifier votre email de confirmation.", + "error": { + "fail": "Compte non créé ! Merci d'essayer à nouveau plus tard.", + "userexists": "Ce compte utilisateur existe déjà ! Veuillez en choisir un autre.", + "emailexists": "Cet email est déjà utilisé ! Veuillez en choisir un autre." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/reset.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/reset.json new file mode 100644 index 0000000..df7c18f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/reset.json @@ -0,0 +1,26 @@ +{ + "reset": { + "request": { + "title": "Réinitialiser son mot de passe", + "form": { + "button": "Réinitialiser le mot de passe" + }, + "messages": { + "info": "Veuillez renseigner l'adresse email utilisée pour vous enregistrer", + "success": "Veuillez vérifier vos nouveaux emails et suivre les instructions pour réinitialiser votre mot de passe." + } + }, + "finish": { + "title": "Réinitialisation du mot de passe", + "form": { + "button": "Réinitialiser le mot de passe" + }, + "messages": { + "info": "Choisir un nouveau mot de passe", + "success": "Votre mot de passe a été réinitialisé. Merci de ", + "keymissing": "La clef de réinitilisation est manquante", + "error": "Votre mot de passe n'a pas pu être réinitialisé. La demande de réinitialisation n'est valable que 24 heures." + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/sessions.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/sessions.json new file mode 100644 index 0000000..1771ca4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/sessions.json @@ -0,0 +1,15 @@ +{ + "sessions": { + "title": "Sessions actives de [{{username}}]", + "table": { + "ipaddress": "Adresse IP", + "useragent": "User Agent", + "date": "Date", + "button": "Invalider" + }, + "messages": { + "success": "La session a été invalidée !", + "error": "Une erreur est survenue ! La session ne peut pas être invalidée." + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/settings.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/settings.json new file mode 100644 index 0000000..e9581dd --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/settings.json @@ -0,0 +1,32 @@ +{ + "settings": { + "title": "Profil de l'utilisateur [{{username}}]", + "form": { + "firstname": "Prénom", + "firstname.placeholder": "Votre prénom", + "lastname": "Nom", + "lastname.placeholder": "Votre nom", + "language": "Langue", + "button": "Sauvegarder" + }, + "messages": { + "error": { + "fail": "Une erreur est survenue ! Votre profil n'a pas été sauvegardé.", + "emailexists": "Cet email est déjà utilisé ! Veuillez en choisir un autre." + }, + "success": "Votre profil a été sauvegardé !", + "validate": { + "firstname": { + "required": "Votre prénom est requis.", + "minlength": "Votre prénom doit comporter au moins un caractère.", + "maxlength": "Votre prénom ne doit pas comporter plus de 50 caractères." + }, + "lastname": { + "required": "Votre nom est requis.", + "minlength": "Votre nom doit comporter au moins un caractère.", + "maxlength": "Votre nom ne doit pas comporter plus de 50 caractères." + } + } + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/user-management.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/user-management.json new file mode 100644 index 0000000..ac338c8 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/i18n/fr/user-management.json @@ -0,0 +1,31 @@ +{ + "userManagement": { + "home": { + "title": "Utilisateurs", + "refreshListLabel": "Actualiser la liste", + "createLabel": "Créer un nouvel utilisateur", + "createOrEditLabel": "Créer ou éditer un utilisateur" + }, + "created": "L'utilisateur {{ param }} a été créé.", + "updated": "L'utilisateur {{ param }} a été mis à jour.", + "deleted": "L'utilisateur {{ param }} a été supprimé.", + "delete": { + "question": "Etes-vous certain de vouloir supprimer l'utilisateur {{ login }} ?" + }, + "detail": { + "title": "Utilisateur" + }, + "login": "Login", + "firstName": "Prénom", + "lastName": "Nom", + "email": "Email", + "activated": "Activé", + "deactivated": "Désactivé", + "profiles": "Droits", + "langKey": "Langue", + "createdBy": "Créé par", + "createdDate": "Créé le", + "lastModifiedBy": "Modifié par", + "lastModifiedDate": "Modifié le" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/index.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/index.html new file mode 100644 index 0000000..38802fe --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/index.html @@ -0,0 +1,127 @@ + + + + + + app + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+ + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/main.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/main.ts new file mode 100644 index 0000000..73c3a0e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/main.ts @@ -0,0 +1 @@ +import('./bootstrap').catch(err => console.error(err)); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/manifest.webapp b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/manifest.webapp new file mode 100644 index 0000000..cb1c30b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/manifest.webapp @@ -0,0 +1,31 @@ +{ + "name": "App", + "short_name": "App", + "icons": [ + { + "src": "./content/images/jhipster_family_member_1_head-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "./content/images/jhipster_family_member_1_head-256.png", + "sizes": "256x256", + "type": "image/png" + }, + { + "src": "./content/images/jhipster_family_member_1_head-384.png", + "sizes": "384x384", + "type": "image/png" + }, + { + "src": "./content/images/jhipster_family_member_1_head-512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#000000", + "background_color": "#e0e0e0", + "start_url": ".", + "display": "standalone", + "orientation": "portrait" +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/robots.txt b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/robots.txt new file mode 100644 index 0000000..361ff60 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/robots.txt @@ -0,0 +1,10 @@ +# robotstxt.org/ + +User-agent: * +Disallow: /api/account +Disallow: /api/account/change-password +Disallow: /api/account/sessions +Disallow: /api/logs/ +Disallow: /api/users/ +Disallow: /management/ +Disallow: /v3/api-docs/ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/dist/images/throbber.gif b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/dist/images/throbber.gif new file mode 100644 index 0000000..0639388 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/dist/images/throbber.gif differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/index.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/index.html new file mode 100644 index 0000000..fc8c55d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/main/webapp/swagger-ui/index.html @@ -0,0 +1,96 @@ + + + + + app - Swagger UI + + + + + + + +
+ + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/gatling.conf b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/gatling.conf new file mode 100644 index 0000000..6916e8b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/gatling.conf @@ -0,0 +1,132 @@ +######################### +# Gatling Configuration # +######################### + +# This file contains all the settings configurable for Gatling with their default values + +gatling { + core { + #outputDirectoryBaseName = "" # The prefix for each simulation result folder (then suffixed by the report generation timestamp) + #runDescription = "" # The description for this simulation run, displayed in each report + #encoding = "utf-8" # Encoding to use throughout Gatling for file and string manipulation + #simulationClass = "" # The FQCN of the simulation to run (when used in conjunction with noReports, the simulation for which assertions will be validated) + #mute = false # When set to true, don't ask for simulation name nor run description (currently only used by Gatling SBT plugin) + #elFileBodiesCacheMaxCapacity = 200 # Cache size for request body EL templates, set to 0 to disable + #rawFileBodiesCacheMaxCapacity = 200 # Cache size for request body Raw templates, set to 0 to disable + #rawFileBodiesInMemoryMaxSize = 1000 # Below this limit, raw file bodies will be cached in memory + + extract { + regex { + #cacheMaxCapacity = 200 # Cache size for the compiled regexes, set to 0 to disable caching + } + xpath { + #cacheMaxCapacity = 200 # Cache size for the compiled XPath queries, set to 0 to disable caching + } + jsonPath { + #cacheMaxCapacity = 200 # Cache size for the compiled jsonPath queries, set to 0 to disable caching + #preferJackson = false # When set to true, prefer Jackson over Boon for JSON-related operations + } + css { + #cacheMaxCapacity = 200 # Cache size for the compiled CSS selectors queries, set to 0 to disable caching + } + } + + directory { + #data = user-files/data # Folder where user's data (e.g. files used by Feeders) is located + #bodies = user-files/bodies # Folder where bodies are located + #simulations = user-files/simulations # Folder where the bundle's simulations are located + #reportsOnly = "" # If set, name of report folder to look for in order to generate its report + #binaries = "" # If set, name of the folder where compiles classes are located: Defaults to GATLING_HOME/target. + #results = results # Name of the folder where all reports folder are located + } + } + charting { + #noReports = false # When set to true, don't generate HTML reports + #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports + #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration. + indicators { + #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary + #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary + #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite + #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite + #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite + #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite + } + } + http { + #fetchedCssCacheMaxCapacity = 200 # Cache size for CSS parsed content, set to 0 to disable + #fetchedHtmlCacheMaxCapacity = 200 # Cache size for HTML parsed content, set to 0 to disable + #perUserCacheMaxCapacity = 200 # Per virtual user cache size, set to 0 to disable + #warmUpUrl = "http://gatling.io" # The URL to use to warm-up the HTTP stack (blank means disabled) + #enableGA = true # Very light Google Analytics, please support + ssl { + keyStore { + #type = "" # Type of SSLContext's KeyManagers store + #file = "" # Location of SSLContext's KeyManagers store + #password = "" # Password for SSLContext's KeyManagers store + #algorithm = "" # Algorithm used SSLContext's KeyManagers store + } + trustStore { + #type = "" # Type of SSLContext's TrustManagers store + #file = "" # Location of SSLContext's TrustManagers store + #password = "" # Password for SSLContext's TrustManagers store + #algorithm = "" # Algorithm used by SSLContext's TrustManagers store + } + } + ahc { + #keepAlive = true # Allow pooling HTTP connections (keep-alive header automatically added) + #connectTimeout = 10000 # Timeout when establishing a connection + #handshakeTimeout = 10000 # Timeout when performing TLS hashshake + #pooledConnectionIdleTimeout = 60000 # Timeout when a connection stays unused in the pool + #readTimeout = 60000 # Timeout when a used connection stays idle + #maxRetry = 2 # Number of times that a request should be tried again + #requestTimeout = 60000 # Timeout of the requests + #acceptAnyCertificate = true # When set to true, doesn't validate SSL certificates + #httpClientCodecMaxInitialLineLength = 4096 # Maximum length of the initial line of the response (e.g. "HTTP/1.0 200 OK") + #httpClientCodecMaxHeaderSize = 8192 # Maximum size, in bytes, of each request's headers + #httpClientCodecMaxChunkSize = 8192 # Maximum length of the content or each chunk + #webSocketMaxFrameSize = 10240000 # Maximum frame payload size + #sslEnabledProtocols = [TLSv1.2, TLSv1.1, TLSv1] # Array of enabled protocols for HTTPS, if empty use the JDK defaults + #sslEnabledCipherSuites = [] # Array of enabled cipher suites for HTTPS, if empty use the AHC defaults + #sslSessionCacheSize = 0 # SSLSession cache size, set to 0 to use JDK's default + #sslSessionTimeout = 0 # SSLSession timeout in seconds, set to 0 to use JDK's default (24h) + #useOpenSsl = false # if OpenSSL should be used instead of JSSE (requires tcnative jar) + #useNativeTransport = false # if native transport should be used instead of Java NIO (requires netty-transport-native-epoll, currently Linux only) + #tcpNoDelay = true + #soReuseAddress = false + #soLinger = -1 + #soSndBuf = -1 + #soRcvBuf = -1 + #allocator = "pooled" # switch to unpooled for unpooled ByteBufAllocator + #maxThreadLocalCharBufferSize = 200000 # Netty's default is 16k + } + dns { + #queryTimeout = 5000 # Timeout of each DNS query in millis + #maxQueriesPerResolve = 6 # Maximum allowed number of DNS queries for a given name resolution + } + } + jms { + #acknowledgedMessagesBufferSize = 5000 # size of the buffer used to tracked acknowledged messages and protect against duplicate receives + } + data { + #writers = [console, file] # The list of DataWriters to which Gatling write simulation data (currently supported : console, file, graphite, jdbc) + console { + #light = false # When set to true, displays a light version without detailed request stats + } + file { + #bufferSize = 8192 # FileDataWriter's internal data buffer size, in bytes + } + leak { + #noActivityTimeout = 30 # Period, in seconds, for which Gatling may have no activity before considering a leak may be happening + } + graphite { + #light = false # only send the all* stats + #host = "localhost" # The host where the Carbon server is located + #port = 2003 # The port to which the Carbon server listens to (2003 is default for plaintext, 2004 is default for pickle) + #protocol = "tcp" # The protocol used to send data to Carbon (currently supported : "tcp", "udp") + #rootPathPrefix = "gatling" # The common prefix of all metrics sent to Graphite + #bufferSize = 8192 # GraphiteDataWriter's internal data buffer size, in bytes + #writeInterval = 1 # GraphiteDataWriter's write interval, in seconds + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/logback.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/logback.xml new file mode 100644 index 0000000..189bcda --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/gatling/conf/logback.xml @@ -0,0 +1,24 @@ + + + + + + + + %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx + false + + + + + + + + + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/IntegrationTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/IntegrationTest.java new file mode 100644 index 0000000..3e8b3ae --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/IntegrationTest.java @@ -0,0 +1,21 @@ +package de.novatec.ittage.app; + +import de.novatec.ittage.app.config.AsyncSyncConfiguration; +import de.novatec.ittage.app.config.EmbeddedSQL; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; + +/** + * Base composite annotation for integration tests. + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@SpringBootTest(classes = { AppApp.class, AsyncSyncConfiguration.class }) +@EmbeddedSQL +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public @interface IntegrationTest { +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/TechnicalStructureTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/TechnicalStructureTest.java new file mode 100644 index 0000000..635f48f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/TechnicalStructureTest.java @@ -0,0 +1,38 @@ +package de.novatec.ittage.app; + +import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue; +import static com.tngtech.archunit.core.domain.JavaClass.Predicates.belongToAnyOf; +import static com.tngtech.archunit.library.Architectures.layeredArchitecture; + +import com.tngtech.archunit.core.importer.ImportOption.DoNotIncludeTests; +import com.tngtech.archunit.junit.AnalyzeClasses; +import com.tngtech.archunit.junit.ArchTest; +import com.tngtech.archunit.lang.ArchRule; + +@AnalyzeClasses(packagesOf = AppApp.class, importOptions = DoNotIncludeTests.class) +class TechnicalStructureTest { + + // prettier-ignore + @ArchTest + static final ArchRule respectsTechnicalArchitectureLayers = layeredArchitecture() + .consideringAllDependencies() + .layer("Config").definedBy("..config..") + .layer("Web").definedBy("..web..") + .optionalLayer("Service").definedBy("..service..") + .layer("Security").definedBy("..security..") + .optionalLayer("Persistence").definedBy("..repository..") + .layer("Domain").definedBy("..domain..") + + .whereLayer("Config").mayNotBeAccessedByAnyLayer() + .whereLayer("Web").mayOnlyBeAccessedByLayers("Config") + .whereLayer("Service").mayOnlyBeAccessedByLayers("Web", "Config") + .whereLayer("Security").mayOnlyBeAccessedByLayers("Config", "Service", "Web") + .whereLayer("Persistence").mayOnlyBeAccessedByLayers("Service", "Security", "Web", "Config") + .whereLayer("Domain").mayOnlyBeAccessedByLayers("Persistence", "Service", "Security", "Web", "Config") + + .ignoreDependency(belongToAnyOf(AppApp.class), alwaysTrue()) + .ignoreDependency(alwaysTrue(), belongToAnyOf( + de.novatec.ittage.app.config.Constants.class, + de.novatec.ittage.app.config.ApplicationProperties.class + )); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/AsyncSyncConfiguration.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/AsyncSyncConfiguration.java new file mode 100644 index 0000000..0546205 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/AsyncSyncConfiguration.java @@ -0,0 +1,15 @@ +package de.novatec.ittage.app.config; + +import java.util.concurrent.Executor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.task.SyncTaskExecutor; + +@Configuration +public class AsyncSyncConfiguration { + + @Bean(name = "taskExecutor") + public Executor taskExecutor() { + return new SyncTaskExecutor(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/EmbeddedSQL.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/EmbeddedSQL.java new file mode 100644 index 0000000..fad5139 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/EmbeddedSQL.java @@ -0,0 +1,11 @@ +package de.novatec.ittage.app.config; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EmbeddedSQL { +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/MysqlTestContainer.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/MysqlTestContainer.java new file mode 100644 index 0000000..7fb771b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/MysqlTestContainer.java @@ -0,0 +1,42 @@ +package de.novatec.ittage.app.config; + +import java.util.Collections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.containers.MySQLContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; + +public class MysqlTestContainer implements SqlTestContainer { + + private static final Logger log = LoggerFactory.getLogger(MysqlTestContainer.class); + + private MySQLContainer mysqlContainer; + + @Override + public void destroy() { + if (null != mysqlContainer && mysqlContainer.isRunning()) { + mysqlContainer.stop(); + } + } + + @Override + public void afterPropertiesSet() { + if (null == mysqlContainer) { + mysqlContainer = + new MySQLContainer<>("mysql:8.2.0") + .withDatabaseName("app") + .withTmpFs(Collections.singletonMap("/testtmpfs", "rw")) + .withLogConsumer(new Slf4jLogConsumer(log)) + .withReuse(true); + } + if (!mysqlContainer.isRunning()) { + mysqlContainer.start(); + } + } + + @Override + public JdbcDatabaseContainer getTestContainer() { + return mysqlContainer; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SpringBootTestClassOrderer.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SpringBootTestClassOrderer.java new file mode 100644 index 0000000..6373182 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SpringBootTestClassOrderer.java @@ -0,0 +1,22 @@ +package de.novatec.ittage.app.config; + +import de.novatec.ittage.app.IntegrationTest; +import java.util.Comparator; +import org.junit.jupiter.api.ClassDescriptor; +import org.junit.jupiter.api.ClassOrderer; +import org.junit.jupiter.api.ClassOrdererContext; + +public class SpringBootTestClassOrderer implements ClassOrderer { + + @Override + public void orderClasses(ClassOrdererContext context) { + context.getClassDescriptors().sort(Comparator.comparingInt(SpringBootTestClassOrderer::getOrder)); + } + + private static int getOrder(ClassDescriptor classDescriptor) { + if (classDescriptor.findAnnotation(IntegrationTest.class).isPresent()) { + return 2; + } + return 1; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainer.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainer.java new file mode 100644 index 0000000..14ad654 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainer.java @@ -0,0 +1,9 @@ +package de.novatec.ittage.app.config; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.InitializingBean; +import org.testcontainers.containers.JdbcDatabaseContainer; + +public interface SqlTestContainer extends InitializingBean, DisposableBean { + JdbcDatabaseContainer getTestContainer(); +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainersSpringContextCustomizerFactory.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainersSpringContextCustomizerFactory.java new file mode 100644 index 0000000..fb85f6e --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/SqlTestContainersSpringContextCustomizerFactory.java @@ -0,0 +1,52 @@ +package de.novatec.ittage.app.config; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.core.annotation.AnnotatedElementUtils; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; + +public class SqlTestContainersSpringContextCustomizerFactory implements ContextCustomizerFactory { + + private Logger log = LoggerFactory.getLogger(SqlTestContainersSpringContextCustomizerFactory.class); + + private static SqlTestContainer prodTestContainer; + + @Override + public ContextCustomizer createContextCustomizer(Class testClass, List configAttributes) { + return (context, mergedConfig) -> { + ConfigurableListableBeanFactory beanFactory = context.getBeanFactory(); + TestPropertyValues testValues = TestPropertyValues.empty(); + EmbeddedSQL sqlAnnotation = AnnotatedElementUtils.findMergedAnnotation(testClass, EmbeddedSQL.class); + if (null != sqlAnnotation) { + log.debug("detected the EmbeddedSQL annotation on class {}", testClass.getName()); + log.info("Warming up the sql database"); + if (null == prodTestContainer) { + try { + Class containerClass = (Class) Class.forName( + this.getClass().getPackageName() + ".MysqlTestContainer" + ); + prodTestContainer = beanFactory.createBean(containerClass); + beanFactory.registerSingleton(containerClass.getName(), prodTestContainer); + // ((DefaultListableBeanFactory)beanFactory).registerDisposableBean(containerClass.getName(), prodTestContainer); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + testValues = + testValues.and( + "spring.datasource.url=" + + prodTestContainer.getTestContainer().getJdbcUrl() + + "?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&createDatabaseIfNotExist=true" + ); + testValues = testValues.and("spring.datasource.username=" + prodTestContainer.getTestContainer().getUsername()); + testValues = testValues.and("spring.datasource.password=" + prodTestContainer.getTestContainer().getPassword()); + } + testValues.applyTo(context); + }; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/StaticResourcesWebConfigurerTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/StaticResourcesWebConfigurerTest.java new file mode 100644 index 0000000..8a7eb34 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/StaticResourcesWebConfigurerTest.java @@ -0,0 +1,76 @@ +package de.novatec.ittage.app.config; + +import static de.novatec.ittage.app.config.StaticResourcesWebConfiguration.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.http.CacheControl; +import org.springframework.mock.web.MockServletContext; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import tech.jhipster.config.JHipsterDefaults; +import tech.jhipster.config.JHipsterProperties; + +class StaticResourcesWebConfigurerTest { + + public static final int MAX_AGE_TEST = 5; + public StaticResourcesWebConfiguration staticResourcesWebConfiguration; + private ResourceHandlerRegistry resourceHandlerRegistry; + private MockServletContext servletContext; + private WebApplicationContext applicationContext; + private JHipsterProperties props; + + @BeforeEach + void setUp() { + servletContext = spy(new MockServletContext()); + applicationContext = mock(WebApplicationContext.class); + resourceHandlerRegistry = spy(new ResourceHandlerRegistry(applicationContext, servletContext)); + props = new JHipsterProperties(); + staticResourcesWebConfiguration = spy(new StaticResourcesWebConfiguration(props)); + } + + @Test + void shouldAppendResourceHandlerAndInitializeIt() { + staticResourcesWebConfiguration.addResourceHandlers(resourceHandlerRegistry); + + verify(resourceHandlerRegistry, times(1)).addResourceHandler(RESOURCE_PATHS); + verify(staticResourcesWebConfiguration, times(1)).initializeResourceHandler(any(ResourceHandlerRegistration.class)); + for (String testingPath : RESOURCE_PATHS) { + assertThat(resourceHandlerRegistry.hasMappingForPattern(testingPath)).isTrue(); + } + } + + @Test + void shouldInitializeResourceHandlerWithCacheControlAndLocations() { + CacheControl ccExpected = CacheControl.maxAge(5, TimeUnit.DAYS).cachePublic(); + when(staticResourcesWebConfiguration.getCacheControl()).thenReturn(ccExpected); + ResourceHandlerRegistration resourceHandlerRegistration = spy(new ResourceHandlerRegistration(RESOURCE_PATHS)); + + staticResourcesWebConfiguration.initializeResourceHandler(resourceHandlerRegistration); + + verify(staticResourcesWebConfiguration, times(1)).getCacheControl(); + verify(resourceHandlerRegistration, times(1)).setCacheControl(ccExpected); + verify(resourceHandlerRegistration, times(1)).addResourceLocations(RESOURCE_LOCATIONS); + } + + @Test + void shouldCreateCacheControlBasedOnJhipsterDefaultProperties() { + CacheControl cacheExpected = CacheControl.maxAge(JHipsterDefaults.Http.Cache.timeToLiveInDays, TimeUnit.DAYS).cachePublic(); + assertThat(staticResourcesWebConfiguration.getCacheControl()) + .extracting(CacheControl::getHeaderValue) + .isEqualTo(cacheExpected.getHeaderValue()); + } + + @Test + void shouldCreateCacheControlWithSpecificConfigurationInProperties() { + props.getHttp().getCache().setTimeToLiveInDays(MAX_AGE_TEST); + CacheControl cacheExpected = CacheControl.maxAge(MAX_AGE_TEST, TimeUnit.DAYS).cachePublic(); + assertThat(staticResourcesWebConfiguration.getCacheControl()) + .extracting(CacheControl::getHeaderValue) + .isEqualTo(cacheExpected.getHeaderValue()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTest.java new file mode 100644 index 0000000..88d985c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTest.java @@ -0,0 +1,132 @@ +package de.novatec.ittage.app.config; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.options; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import jakarta.servlet.*; +import java.io.File; +import java.util.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.embedded.undertow.UndertowServletWebServerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.mock.env.MockEnvironment; +import org.springframework.mock.web.MockServletContext; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import tech.jhipster.config.JHipsterConstants; +import tech.jhipster.config.JHipsterProperties; + +/** + * Unit tests for the {@link WebConfigurer} class. + */ +class WebConfigurerTest { + + private WebConfigurer webConfigurer; + + private MockServletContext servletContext; + + private MockEnvironment env; + + private JHipsterProperties props; + + @BeforeEach + public void setup() { + servletContext = spy(new MockServletContext()); + doReturn(mock(FilterRegistration.Dynamic.class)).when(servletContext).addFilter(anyString(), any(Filter.class)); + doReturn(mock(ServletRegistration.Dynamic.class)).when(servletContext).addServlet(anyString(), any(Servlet.class)); + + env = new MockEnvironment(); + props = new JHipsterProperties(); + + webConfigurer = new WebConfigurer(env, props); + } + + @Test + void shouldCustomizeServletContainer() { + env.setActiveProfiles(JHipsterConstants.SPRING_PROFILE_PRODUCTION); + UndertowServletWebServerFactory container = new UndertowServletWebServerFactory(); + webConfigurer.customize(container); + assertThat(container.getMimeMappings().get("abs")).isEqualTo("audio/x-mpeg"); + assertThat(container.getMimeMappings().get("html")).isEqualTo("text/html"); + assertThat(container.getMimeMappings().get("json")).isEqualTo("application/json"); + if (container.getDocumentRoot() != null) { + assertThat(container.getDocumentRoot()).isEqualTo(new File("target/classes/static/")); + } + } + + @Test + void shouldCorsFilterOnApiPath() throws Exception { + props.getCors().setAllowedOrigins(Collections.singletonList("other.domain.com")); + props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + props.getCors().setAllowedHeaders(Collections.singletonList("*")); + props.getCors().setMaxAge(1800L); + props.getCors().setAllowCredentials(true); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); + + mockMvc + .perform( + options("/api/test-cors") + .header(HttpHeaders.ORIGIN, "other.domain.com") + .header(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST") + ) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")) + .andExpect(header().string(HttpHeaders.VARY, "Origin")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true")) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "1800")); + + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().string(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "other.domain.com")); + } + + @Test + void shouldCorsFilterOnOtherPath() throws Exception { + props.getCors().setAllowedOrigins(Collections.singletonList("*")); + props.getCors().setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE")); + props.getCors().setAllowedHeaders(Collections.singletonList("*")); + props.getCors().setMaxAge(1800L); + props.getCors().setAllowCredentials(true); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); + + mockMvc + .perform(get("/test/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + void shouldCorsFilterDeactivatedForNullAllowedOrigins() throws Exception { + props.getCors().setAllowedOrigins(null); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); + + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } + + @Test + void shouldCorsFilterDeactivatedForEmptyAllowedOrigins() throws Exception { + props.getCors().setAllowedOrigins(new ArrayList<>()); + + MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new WebConfigurerTestController()).addFilters(webConfigurer.corsFilter()).build(); + + mockMvc + .perform(get("/api/test-cors").header(HttpHeaders.ORIGIN, "other.domain.com")) + .andExpect(status().isOk()) + .andExpect(header().doesNotExist(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTestController.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTestController.java new file mode 100644 index 0000000..92830c0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/WebConfigurerTestController.java @@ -0,0 +1,14 @@ +package de.novatec.ittage.app.config; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class WebConfigurerTestController { + + @GetMapping("/api/test-cors") + public void testCorsOnApiPath() {} + + @GetMapping("/test/test-cors") + public void testCorsOnOtherPath() {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/timezone/HibernateTimeZoneIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/timezone/HibernateTimeZoneIT.java new file mode 100644 index 0000000..a947059 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/config/timezone/HibernateTimeZoneIT.java @@ -0,0 +1,172 @@ +package de.novatec.ittage.app.config.timezone; + +import static java.lang.String.format; +import static org.assertj.core.api.Assertions.assertThat; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.repository.timezone.DateTimeWrapper; +import de.novatec.ittage.app.repository.timezone.DateTimeWrapperRepository; +import java.time.*; +import java.time.format.DateTimeFormatter; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.support.rowset.SqlRowSet; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for verifying the behavior of Hibernate in the context of storing various date and time types across different databases. + * The tests focus on ensuring that the stored values are correctly transformed and stored according to the configured timezone. + * Timezone is environment specific, and can be adjusted according to your needs. + * + * For more context, refer to: + * - GitHub Issue: https://github.com/jhipster/generator-jhipster/issues/22579 + * - Pull Request: https://github.com/jhipster/generator-jhipster/pull/22946 + */ +@IntegrationTest +class HibernateTimeZoneIT { + + @Autowired + private DateTimeWrapperRepository dateTimeWrapperRepository; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Value("${spring.jpa.properties.hibernate.jdbc.time_zone:UTC}") + private String zoneId; + + private DateTimeWrapper dateTimeWrapper; + private DateTimeFormatter dateTimeFormatter; + private DateTimeFormatter timeFormatter; + private DateTimeFormatter offsetTimeFormatter; + private DateTimeFormatter dateFormatter; + + @BeforeEach + public void setup() { + dateTimeWrapper = new DateTimeWrapper(); + dateTimeWrapper.setInstant(Instant.parse("2014-11-12T05:10:00.0Z")); + dateTimeWrapper.setLocalDateTime(LocalDateTime.parse("2014-11-12T07:20:00.0")); + dateTimeWrapper.setOffsetDateTime(OffsetDateTime.parse("2011-12-14T08:30:00.0Z")); + dateTimeWrapper.setZonedDateTime(ZonedDateTime.parse("2011-12-14T08:40:00.0Z")); + dateTimeWrapper.setLocalTime(LocalTime.parse("14:50:00")); + dateTimeWrapper.setOffsetTime(OffsetTime.parse("14:00:00+02:00")); + dateTimeWrapper.setLocalDate(LocalDate.parse("2016-09-10")); + + dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S").withZone(ZoneId.of(zoneId)); + timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss").withZone(ZoneId.of(zoneId)); + offsetTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss"); + dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + } + + @Test + @Transactional + void storeInstantWithZoneIdConfigShouldBeStoredOnConfiguredTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("instant", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeFormatter.format(dateTimeWrapper.getInstant()); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeLocalDateTimeWithZoneIdConfigShouldBeStoredOnConfiguredTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper.getLocalDateTime().atZone(ZoneId.systemDefault()).format(dateTimeFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeOffsetDateTimeWithZoneIdConfigShouldBeStoredOnConfiguredTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("offset_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper.getOffsetDateTime().format(dateTimeFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeZoneDateTimeWithZoneIdConfigShouldBeStoredOnConfiguredTimeZone() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("zoned_date_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper.getZonedDateTime().format(dateTimeFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeLocalTimeWithZoneIdConfigShouldBeStoredOnConfiguredTimeZoneAccordingToHis1stJan1970Value() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getLocalTime() + .atDate(LocalDate.of(1970, Month.JANUARY, 1)) + .atZone(ZoneId.systemDefault()) + .format(timeFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeOffsetTimeWithZoneIdConfigShouldBeStoredOnConfiguredTimeZoneAccordingToHis1stJan1970Value() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("offset_time", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper + .getOffsetTime() + // Convert to configured timezone + .withOffsetSameInstant(ZoneId.of(zoneId).getRules().getOffset(Instant.now())) + // Normalize to System TimeZone. + // TODO this behavior looks a bug, refer to https://github.com/jhipster/generator-jhipster/issues/22579. + .withOffsetSameLocal(OffsetDateTime.ofInstant(Instant.EPOCH, ZoneId.systemDefault()).getOffset()) + // Convert the normalized value to configured timezone + .withOffsetSameInstant(ZoneId.of(zoneId).getRules().getOffset(Instant.EPOCH)) + .format(offsetTimeFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + @Test + @Transactional + void storeLocalDateWithZoneIdConfigShouldBeStoredWithoutTransformation() { + dateTimeWrapperRepository.saveAndFlush(dateTimeWrapper); + + String request = generateSqlRequest("local_date", dateTimeWrapper.getId()); + SqlRowSet resultSet = jdbcTemplate.queryForRowSet(request); + String expectedValue = dateTimeWrapper.getLocalDate().format(dateFormatter); + + assertThatValueFromSqlRowSetIsEqualToExpectedValue(resultSet, expectedValue); + } + + private String generateSqlRequest(String fieldName, long id) { + return format("SELECT %s FROM jhi_date_time_wrapper where id=%d", fieldName, id); + } + + private void assertThatValueFromSqlRowSetIsEqualToExpectedValue(SqlRowSet sqlRowSet, String expectedValue) { + while (sqlRowSet.next()) { + String dbValue = sqlRowSet.getString(1); + + assertThat(dbValue).isNotNull(); + assertThat(dbValue).isEqualTo(expectedValue); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/management/SecurityMetersServiceTests.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/management/SecurityMetersServiceTests.java new file mode 100644 index 0000000..a29cce0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/management/SecurityMetersServiceTests.java @@ -0,0 +1,70 @@ +package de.novatec.ittage.app.management; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.util.Collection; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SecurityMetersServiceTests { + + private static final String INVALID_TOKENS_METER_EXPECTED_NAME = "security.authentication.invalid-tokens"; + + private MeterRegistry meterRegistry; + + private SecurityMetersService securityMetersService; + + @BeforeEach + public void setup() { + meterRegistry = new SimpleMeterRegistry(); + + securityMetersService = new SecurityMetersService(meterRegistry); + } + + @Test + void testInvalidTokensCountersByCauseAreCreated() { + meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).counter(); + + meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter(); + + meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter(); + + meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter(); + + meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter(); + + Collection counters = meterRegistry.find(INVALID_TOKENS_METER_EXPECTED_NAME).counters(); + + assertThat(counters).hasSize(4); + } + + @Test + void testCountMethodsShouldBeBoundToCorrectCounters() { + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isZero(); + + securityMetersService.trackTokenExpired(); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isEqualTo(1); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isZero(); + + securityMetersService.trackTokenUnsupported(); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "unsupported").counter().count()).isEqualTo(1); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isZero(); + + securityMetersService.trackTokenInvalidSignature(); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()).isEqualTo(1); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isZero(); + + securityMetersService.trackTokenMalformed(); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isEqualTo(1); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapper.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapper.java new file mode 100644 index 0000000..9085009 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapper.java @@ -0,0 +1,132 @@ +package de.novatec.ittage.app.repository.timezone; + +import jakarta.persistence.*; +import java.io.Serializable; +import java.time.*; +import java.util.Objects; + +@Entity +@Table(name = "jhi_date_time_wrapper") +public class DateTimeWrapper implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "instant") + private Instant instant; + + @Column(name = "local_date_time") + private LocalDateTime localDateTime; + + @Column(name = "offset_date_time") + private OffsetDateTime offsetDateTime; + + @Column(name = "zoned_date_time") + private ZonedDateTime zonedDateTime; + + @Column(name = "local_time") + private LocalTime localTime; + + @Column(name = "offset_time") + private OffsetTime offsetTime; + + @Column(name = "local_date") + private LocalDate localDate; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Instant getInstant() { + return instant; + } + + public void setInstant(Instant instant) { + this.instant = instant; + } + + public LocalDateTime getLocalDateTime() { + return localDateTime; + } + + public void setLocalDateTime(LocalDateTime localDateTime) { + this.localDateTime = localDateTime; + } + + public OffsetDateTime getOffsetDateTime() { + return offsetDateTime; + } + + public void setOffsetDateTime(OffsetDateTime offsetDateTime) { + this.offsetDateTime = offsetDateTime; + } + + public ZonedDateTime getZonedDateTime() { + return zonedDateTime; + } + + public void setZonedDateTime(ZonedDateTime zonedDateTime) { + this.zonedDateTime = zonedDateTime; + } + + public LocalTime getLocalTime() { + return localTime; + } + + public void setLocalTime(LocalTime localTime) { + this.localTime = localTime; + } + + public OffsetTime getOffsetTime() { + return offsetTime; + } + + public void setOffsetTime(OffsetTime offsetTime) { + this.offsetTime = offsetTime; + } + + public LocalDate getLocalDate() { + return localDate; + } + + public void setLocalDate(LocalDate localDate) { + this.localDate = localDate; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + DateTimeWrapper dateTimeWrapper = (DateTimeWrapper) o; + return !(dateTimeWrapper.getId() == null || getId() == null) && Objects.equals(getId(), dateTimeWrapper.getId()); + } + + @Override + public int hashCode() { + return Objects.hashCode(getId()); + } + + // prettier-ignore + @Override + public String toString() { + return "TimeZoneTest{" + + "id=" + id + + ", instant=" + instant + + ", localDateTime=" + localDateTime + + ", offsetDateTime=" + offsetDateTime + + ", zonedDateTime=" + zonedDateTime + + '}'; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapperRepository.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapperRepository.java new file mode 100644 index 0000000..cbc6d87 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/repository/timezone/DateTimeWrapperRepository.java @@ -0,0 +1,10 @@ +package de.novatec.ittage.app.repository.timezone; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Spring Data JPA repository for the {@link DateTimeWrapper} entity. + */ +@Repository +public interface DateTimeWrapperRepository extends JpaRepository {} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/DomainUserDetailsServiceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/DomainUserDetailsServiceIT.java new file mode 100644 index 0000000..3b78f78 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/DomainUserDetailsServiceIT.java @@ -0,0 +1,113 @@ +package de.novatec.ittage.app.security; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import java.util.Locale; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integrations tests for {@link DomainUserDetailsService}. + */ +@Transactional +@IntegrationTest +class DomainUserDetailsServiceIT { + + private static final String USER_ONE_LOGIN = "test-user-one"; + private static final String USER_ONE_EMAIL = "test-user-one@localhost"; + private static final String USER_TWO_LOGIN = "test-user-two"; + private static final String USER_TWO_EMAIL = "test-user-two@localhost"; + private static final String USER_THREE_LOGIN = "test-user-three"; + private static final String USER_THREE_EMAIL = "test-user-three@localhost"; + + @Autowired + private UserRepository userRepository; + + @Autowired + @Qualifier("userDetailsService") + private UserDetailsService domainUserDetailsService; + + @BeforeEach + public void init() { + User userOne = new User(); + userOne.setLogin(USER_ONE_LOGIN); + userOne.setPassword(RandomStringUtils.randomAlphanumeric(60)); + userOne.setActivated(true); + userOne.setEmail(USER_ONE_EMAIL); + userOne.setFirstName("userOne"); + userOne.setLastName("doe"); + userOne.setLangKey("en"); + userRepository.save(userOne); + + User userTwo = new User(); + userTwo.setLogin(USER_TWO_LOGIN); + userTwo.setPassword(RandomStringUtils.randomAlphanumeric(60)); + userTwo.setActivated(true); + userTwo.setEmail(USER_TWO_EMAIL); + userTwo.setFirstName("userTwo"); + userTwo.setLastName("doe"); + userTwo.setLangKey("en"); + userRepository.save(userTwo); + + User userThree = new User(); + userThree.setLogin(USER_THREE_LOGIN); + userThree.setPassword(RandomStringUtils.randomAlphanumeric(60)); + userThree.setActivated(false); + userThree.setEmail(USER_THREE_EMAIL); + userThree.setFirstName("userThree"); + userThree.setLastName("doe"); + userThree.setLangKey("en"); + userRepository.save(userThree); + } + + @Test + void assertThatUserCanBeFoundByLogin() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByLoginIgnoreCase() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_LOGIN.toUpperCase(Locale.ENGLISH)); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByEmail() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); + } + + @Test + void assertThatUserCanBeFoundByEmailIgnoreCase() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_TWO_EMAIL.toUpperCase(Locale.ENGLISH)); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_TWO_LOGIN); + } + + @Test + void assertThatEmailIsPrioritizedOverLogin() { + UserDetails userDetails = domainUserDetailsService.loadUserByUsername(USER_ONE_EMAIL); + assertThat(userDetails).isNotNull(); + assertThat(userDetails.getUsername()).isEqualTo(USER_ONE_LOGIN); + } + + @Test + void assertThatUserNotActivatedExceptionIsThrownForNotActivatedUsers() { + assertThatExceptionOfType(UserNotActivatedException.class) + .isThrownBy(() -> domainUserDetailsService.loadUserByUsername(USER_THREE_LOGIN)); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/SecurityUtilsUnitTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/SecurityUtilsUnitTest.java new file mode 100644 index 0000000..dd09755 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/SecurityUtilsUnitTest.java @@ -0,0 +1,101 @@ +package de.novatec.ittage.app.security; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Optional; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +/** + * Test class for the {@link SecurityUtils} utility class. + */ +class SecurityUtilsUnitTest { + + @BeforeEach + @AfterEach + void cleanup() { + SecurityContextHolder.clearContext(); + } + + @Test + void testGetCurrentUserLogin() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + Optional login = SecurityUtils.getCurrentUserLogin(); + assertThat(login).contains("admin"); + } + + @Test + void testGetCurrentUserJWT() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "token")); + SecurityContextHolder.setContext(securityContext); + Optional jwt = SecurityUtils.getCurrentUserJWT(); + assertThat(jwt).contains("token"); + } + + @Test + void testIsAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("admin", "admin")); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isTrue(); + } + + @Test + void testAnonymousIsNotAuthenticated() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.ANONYMOUS)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("anonymous", "anonymous", authorities)); + SecurityContextHolder.setContext(securityContext); + boolean isAuthenticated = SecurityUtils.isAuthenticated(); + assertThat(isAuthenticated).isFalse(); + } + + @Test + void testHasCurrentUserThisAuthority() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.USER)).isTrue(); + assertThat(SecurityUtils.hasCurrentUserThisAuthority(AuthoritiesConstants.ADMIN)).isFalse(); + } + + @Test + void testHasCurrentUserAnyOfAuthorities() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isTrue(); + assertThat(SecurityUtils.hasCurrentUserAnyOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isFalse(); + } + + @Test + void testHasCurrentUserNoneOfAuthorities() { + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + Collection authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority(AuthoritiesConstants.USER)); + securityContext.setAuthentication(new UsernamePasswordAuthenticationToken("user", "user", authorities)); + SecurityContextHolder.setContext(securityContext); + + assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN)).isFalse(); + assertThat(SecurityUtils.hasCurrentUserNoneOfAuthorities(AuthoritiesConstants.ANONYMOUS, AuthoritiesConstants.ADMIN)).isTrue(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/AuthenticationIntegrationTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/AuthenticationIntegrationTest.java new file mode 100644 index 0000000..75fdab4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/AuthenticationIntegrationTest.java @@ -0,0 +1,35 @@ +package de.novatec.ittage.app.security.jwt; + +import de.novatec.ittage.app.config.SecurityConfiguration; +import de.novatec.ittage.app.config.SecurityJwtConfiguration; +import de.novatec.ittage.app.config.WebConfigurer; +import de.novatec.ittage.app.management.SecurityMetersService; +import de.novatec.ittage.app.web.rest.AuthenticateController; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import tech.jhipster.config.JHipsterProperties; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@SpringBootTest( + properties = { + "jhipster.security.authentication.jwt.base64-secret=fd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8", + "jhipster.security.authentication.jwt.token-validity-in-seconds=60000", + }, + classes = { + JHipsterProperties.class, + WebConfigurer.class, + SecurityConfiguration.class, + SecurityJwtConfiguration.class, + SecurityMetersService.class, + AuthenticateController.class, + JwtAuthenticationTestUtils.class, + } +) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +public @interface AuthenticationIntegrationTest { +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/JwtAuthenticationTestUtils.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/JwtAuthenticationTestUtils.java new file mode 100644 index 0000000..3b12fb1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/JwtAuthenticationTestUtils.java @@ -0,0 +1,106 @@ +package de.novatec.ittage.app.security.jwt; + +import static de.novatec.ittage.app.security.SecurityUtils.AUTHORITIES_KEY; +import static de.novatec.ittage.app.security.SecurityUtils.JWT_ALGORITHM; + +import com.nimbusds.jose.jwk.source.ImmutableSecret; +import com.nimbusds.jose.util.Base64; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import java.time.Instant; +import java.util.Collections; +import javax.crypto.Mac; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import org.springframework.context.annotation.Bean; +import org.springframework.security.crypto.codec.Hex; +import org.springframework.security.oauth2.jwt.JwsHeader; +import org.springframework.security.oauth2.jwt.JwtClaimsSet; +import org.springframework.security.oauth2.jwt.JwtEncoder; +import org.springframework.security.oauth2.jwt.JwtEncoderParameters; +import org.springframework.security.oauth2.jwt.NimbusJwtEncoder; +import org.springframework.web.servlet.handler.HandlerMappingIntrospector; + +public class JwtAuthenticationTestUtils { + + public static final String BEARER = "Bearer "; + + @Bean + private HandlerMappingIntrospector mvcHandlerMappingIntrospector() { + return new HandlerMappingIntrospector(); + } + + @Bean + private MeterRegistry meterRegistry() { + return new SimpleMeterRegistry(); + } + + public static String createValidToken(String jwtKey) { + return createValidTokenForUser(jwtKey, "anonymous"); + } + + public static String createValidTokenForUser(String jwtKey, String user) { + JwtEncoder encoder = jwtEncoder(jwtKey); + + var now = Instant.now(); + + JwtClaimsSet claims = JwtClaimsSet + .builder() + .issuedAt(now) + .expiresAt(now.plusSeconds(60)) + .subject(user) + .claims(customClain -> customClain.put(AUTHORITIES_KEY, Collections.singletonList("ROLE_ADMIN"))) + .build(); + + JwsHeader jwsHeader = JwsHeader.with(JWT_ALGORITHM).build(); + return encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); + } + + public static String createTokenWithDifferentSignature() { + JwtEncoder encoder = jwtEncoder("Xfd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8"); + + var now = Instant.now(); + var past = now.plusSeconds(60); + + JwtClaimsSet claims = JwtClaimsSet.builder().issuedAt(now).expiresAt(past).subject("anonymous").build(); + + JwsHeader jwsHeader = JwsHeader.with(JWT_ALGORITHM).build(); + return encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); + } + + public static String createExpiredToken(String jwtKey) { + JwtEncoder encoder = jwtEncoder(jwtKey); + + var now = Instant.now(); + var past = now.minusSeconds(600); + + JwtClaimsSet claims = JwtClaimsSet.builder().issuedAt(past).expiresAt(past.plusSeconds(1)).subject("anonymous").build(); + + JwsHeader jwsHeader = JwsHeader.with(JWT_ALGORITHM).build(); + return encoder.encode(JwtEncoderParameters.from(jwsHeader, claims)).getTokenValue(); + } + + public static String createInvalidToken(String jwtKey) throws Exception { + return createValidToken(jwtKey).substring(1); + } + + public static String createSignedInvalidJwt(String jwtKey) throws Exception { + return calculateHMAC("foo", jwtKey); + } + + private static JwtEncoder jwtEncoder(String jwtKey) { + return new NimbusJwtEncoder(new ImmutableSecret<>(getSecretKey(jwtKey))); + } + + private static SecretKey getSecretKey(String jwtKey) { + byte[] keyBytes = Base64.from(jwtKey).decode(); + return new SecretKeySpec(keyBytes, 0, keyBytes.length, JWT_ALGORITHM.getName()); + } + + private static String calculateHMAC(String data, String key) throws Exception { + SecretKeySpec secretKeySpec = new SecretKeySpec(Base64.from(key).decode(), "HmacSHA512"); + Mac mac = Mac.getInstance("HmacSHA512"); + mac.init(secretKeySpec); + return String.copyValueOf(Hex.encode(mac.doFinal(data.getBytes()))); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationIT.java new file mode 100644 index 0000000..07d7df4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationIT.java @@ -0,0 +1,53 @@ +package de.novatec.ittage.app.security.jwt; + +import static de.novatec.ittage.app.security.jwt.JwtAuthenticationTestUtils.*; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@AutoConfigureMockMvc +@AuthenticationIntegrationTest +class TokenAuthenticationIT { + + @Autowired + private MockMvc mvc; + + @Value("${jhipster.security.authentication.jwt.base64-secret}") + private String jwtKey; + + @Test + void testLoginWithValidToken() throws Exception { + expectOk(createValidToken(jwtKey)); + } + + @Test + void testReturnFalseWhenJWThasInvalidSignature() throws Exception { + expectUnauthorized(createTokenWithDifferentSignature()); + } + + @Test + void testReturnFalseWhenJWTisMalformed() throws Exception { + expectUnauthorized(createSignedInvalidJwt(jwtKey)); + } + + @Test + void testReturnFalseWhenJWTisExpired() throws Exception { + expectUnauthorized(createExpiredToken(jwtKey)); + } + + private void expectOk(String token) throws Exception { + mvc.perform(MockMvcRequestBuilders.get("/api/authenticate").header(AUTHORIZATION, BEARER + token)).andExpect(status().isOk()); + } + + private void expectUnauthorized(String token) throws Exception { + mvc + .perform(MockMvcRequestBuilders.get("/api/authenticate").header(AUTHORIZATION, BEARER + token)) + .andExpect(status().isUnauthorized()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationSecurityMetersIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationSecurityMetersIT.java new file mode 100644 index 0000000..83a5d5f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/security/jwt/TokenAuthenticationSecurityMetersIT.java @@ -0,0 +1,87 @@ +package de.novatec.ittage.app.security.jwt; + +import static de.novatec.ittage.app.security.jwt.JwtAuthenticationTestUtils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.http.HttpHeaders.AUTHORIZATION; + +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import java.util.Collection; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; + +@AutoConfigureMockMvc +@AuthenticationIntegrationTest +class TokenAuthenticationSecurityMetersIT { + + private static final String INVALID_TOKENS_METER_EXPECTED_NAME = "security.authentication.invalid-tokens"; + + @Autowired + private MockMvc mvc; + + @Value("${jhipster.security.authentication.jwt.base64-secret}") + private String jwtKey; + + @Autowired + private MeterRegistry meterRegistry; + + @Test + void testValidTokenShouldNotCountAnything() throws Exception { + Collection counters = meterRegistry.find(INVALID_TOKENS_METER_EXPECTED_NAME).counters(); + + var count = aggregate(counters); + + tryToAuthenticate(createValidToken(jwtKey)); + + assertThat(aggregate(counters)).isEqualTo(count); + } + + @Test + void testTokenExpiredCount() throws Exception { + var count = meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count(); + + tryToAuthenticate(createExpiredToken(jwtKey)); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "expired").counter().count()).isEqualTo(count + 1); + } + + @Test + void testTokenSignatureInvalidCount() throws Exception { + var count = meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count(); + + tryToAuthenticate(createTokenWithDifferentSignature()); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "invalid-signature").counter().count()) + .isEqualTo(count + 1); + } + + @Test + void testTokenMalformedCount() throws Exception { + var count = meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count(); + + tryToAuthenticate(createSignedInvalidJwt(jwtKey)); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isEqualTo(count + 1); + } + + @Test + void testTokenInvalidCount() throws Exception { + var count = meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count(); + + tryToAuthenticate(createInvalidToken(jwtKey)); + + assertThat(meterRegistry.get(INVALID_TOKENS_METER_EXPECTED_NAME).tag("cause", "malformed").counter().count()).isEqualTo(count + 1); + } + + private void tryToAuthenticate(String token) throws Exception { + mvc.perform(MockMvcRequestBuilders.get("/api/authenticate").header(AUTHORIZATION, BEARER + token)); + } + + private double aggregate(Collection counters) { + return counters.stream().mapToDouble(Counter::count).sum(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/MailServiceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/MailServiceIT.java new file mode 100644 index 0000000..ff60719 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/MailServiceIT.java @@ -0,0 +1,238 @@ +package de.novatec.ittage.app.service; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.config.Constants; +import de.novatec.ittage.app.domain.User; +import jakarta.mail.Multipart; +import jakarta.mail.Session; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; +import tech.jhipster.config.JHipsterProperties; + +/** + * Integration tests for {@link MailService}. + */ +@IntegrationTest +class MailServiceIT { + + private static final String[] languages = { + "en", + "fr", + "de", + // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array + }; + private static final Pattern PATTERN_LOCALE_3 = Pattern.compile("([a-z]{2})-([a-zA-Z]{4})-([a-z]{2})"); + private static final Pattern PATTERN_LOCALE_2 = Pattern.compile("([a-z]{2})-([a-z]{2})"); + + @Autowired + private JHipsterProperties jHipsterProperties; + + @MockBean + private JavaMailSender javaMailSender; + + @Captor + private ArgumentCaptor messageCaptor; + + @Autowired + private MailService mailService; + + @BeforeEach + public void setup() { + doNothing().when(javaMailSender).send(any(MimeMessage.class)); + when(javaMailSender.createMimeMessage()).thenReturn(new MimeMessage((Session) null)); + } + + @Test + void testSendEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(String.class); + assertThat(message.getContent()).hasToString("testContent"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); + } + + @Test + void testSendHtmlEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, true); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(String.class); + assertThat(message.getContent()).hasToString("testContent"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendMultipartEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, false); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + MimeMultipart mp = (MimeMultipart) message.getContent(); + MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); + ByteArrayOutputStream aos = new ByteArrayOutputStream(); + part.writeTo(aos); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(Multipart.class); + assertThat(aos).hasToString("\r\ntestContent"); + assertThat(part.getDataHandler().getContentType()).isEqualTo("text/plain; charset=UTF-8"); + } + + @Test + void testSendMultipartHtmlEmail() throws Exception { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", true, true); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + MimeMultipart mp = (MimeMultipart) message.getContent(); + MimeBodyPart part = (MimeBodyPart) ((MimeMultipart) mp.getBodyPart(0).getContent()).getBodyPart(0); + ByteArrayOutputStream aos = new ByteArrayOutputStream(); + part.writeTo(aos); + assertThat(message.getSubject()).isEqualTo("testSubject"); + assertThat(message.getAllRecipients()[0]).hasToString("john.doe@example.com"); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent()).isInstanceOf(Multipart.class); + assertThat(aos).hasToString("\r\ntestContent"); + assertThat(part.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendEmailFromTemplate() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getSubject()).isEqualTo("test title"); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isEqualToNormalizingNewlines("test title, http://127.0.0.1:8080, john\n"); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendActivationEmail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendActivationEmail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testCreationEmail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendCreationEmail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendPasswordResetMail() throws Exception { + User user = new User(); + user.setLangKey(Constants.DEFAULT_LANGUAGE); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + mailService.sendPasswordResetMail(user); + verify(javaMailSender).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + assertThat(message.getAllRecipients()[0]).hasToString(user.getEmail()); + assertThat(message.getFrom()[0]).hasToString(jHipsterProperties.getMail().getFrom()); + assertThat(message.getContent().toString()).isNotEmpty(); + assertThat(message.getDataHandler().getContentType()).isEqualTo("text/html;charset=UTF-8"); + } + + @Test + void testSendEmailWithException() { + doThrow(MailSendException.class).when(javaMailSender).send(any(MimeMessage.class)); + try { + mailService.sendEmail("john.doe@example.com", "testSubject", "testContent", false, false); + } catch (Exception e) { + fail("Exception shouldn't have been thrown"); + } + } + + @Test + void testSendLocalizedEmailForAllSupportedLanguages() throws Exception { + User user = new User(); + user.setLogin("john"); + user.setEmail("john.doe@example.com"); + for (String langKey : languages) { + user.setLangKey(langKey); + mailService.sendEmailFromTemplate(user, "mail/testEmail", "email.test.title"); + verify(javaMailSender, atLeastOnce()).send(messageCaptor.capture()); + MimeMessage message = messageCaptor.getValue(); + + String propertyFilePath = "i18n/messages_" + getMessageSourceSuffixForLanguage(langKey) + ".properties"; + URL resource = this.getClass().getClassLoader().getResource(propertyFilePath); + File file = new File(new URI(resource.getFile()).getPath()); + Properties properties = new Properties(); + properties.load(new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"))); + + String emailTitle = (String) properties.get("email.test.title"); + assertThat(message.getSubject()).isEqualTo(emailTitle); + assertThat(message.getContent().toString()) + .isEqualToNormalizingNewlines("" + emailTitle + ", http://127.0.0.1:8080, john\n"); + } + } + + /** + * Convert a lang key to the Java locale. + */ + private String getMessageSourceSuffixForLanguage(String langKey) { + String javaLangKey = langKey; + Matcher matcher2 = PATTERN_LOCALE_2.matcher(langKey); + if (matcher2.matches()) { + javaLangKey = matcher2.group(1) + "_" + matcher2.group(2).toUpperCase(); + } + Matcher matcher3 = PATTERN_LOCALE_3.matcher(langKey); + if (matcher3.matches()) { + javaLangKey = matcher3.group(1) + "_" + matcher3.group(2) + "_" + matcher3.group(3).toUpperCase(); + } + return javaLangKey; + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/UserServiceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/UserServiceIT.java new file mode 100644 index 0000000..4bdb997 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/UserServiceIT.java @@ -0,0 +1,181 @@ +package de.novatec.ittage.app.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Optional; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.auditing.AuditingHandler; +import org.springframework.data.auditing.DateTimeProvider; +import org.springframework.transaction.annotation.Transactional; +import tech.jhipster.security.RandomUtil; + +/** + * Integration tests for {@link UserService}. + */ +@IntegrationTest +@Transactional +class UserServiceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + + private static final String DEFAULT_LASTNAME = "doe"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + + private static final String DEFAULT_LANGKEY = "dummy"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserService userService; + + @Autowired + private AuditingHandler auditingHandler; + + @MockBean + private DateTimeProvider dateTimeProvider; + + private User user; + + @BeforeEach + public void init() { + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + user.setEmail(DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + + when(dateTimeProvider.getNow()).thenReturn(Optional.of(LocalDateTime.now())); + auditingHandler.setDateTimeProvider(dateTimeProvider); + } + + @Test + @Transactional + void assertThatUserMustExistToResetPassword() { + userRepository.saveAndFlush(user); + Optional maybeUser = userService.requestPasswordReset("invalid.login@localhost"); + assertThat(maybeUser).isNotPresent(); + + maybeUser = userService.requestPasswordReset(user.getEmail()); + assertThat(maybeUser).isPresent(); + assertThat(maybeUser.orElse(null).getEmail()).isEqualTo(user.getEmail()); + assertThat(maybeUser.orElse(null).getResetDate()).isNotNull(); + assertThat(maybeUser.orElse(null).getResetKey()).isNotNull(); + } + + @Test + @Transactional + void assertThatOnlyActivatedUserCanRequestPasswordReset() { + user.setActivated(false); + userRepository.saveAndFlush(user); + + Optional maybeUser = userService.requestPasswordReset(user.getLogin()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatResetKeyMustNotBeOlderThan24Hours() { + Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + userRepository.saveAndFlush(user); + + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatResetKeyMustBeValid() { + Instant daysAgo = Instant.now().minus(25, ChronoUnit.HOURS); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey("1234"); + userRepository.saveAndFlush(user); + + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isNotPresent(); + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatUserCanResetPassword() { + String oldPassword = user.getPassword(); + Instant daysAgo = Instant.now().minus(2, ChronoUnit.HOURS); + String resetKey = RandomUtil.generateResetKey(); + user.setActivated(true); + user.setResetDate(daysAgo); + user.setResetKey(resetKey); + userRepository.saveAndFlush(user); + + Optional maybeUser = userService.completePasswordReset("johndoe2", user.getResetKey()); + assertThat(maybeUser).isPresent(); + assertThat(maybeUser.orElse(null).getResetDate()).isNull(); + assertThat(maybeUser.orElse(null).getResetKey()).isNull(); + assertThat(maybeUser.orElse(null).getPassword()).isNotEqualTo(oldPassword); + + userRepository.delete(user); + } + + @Test + @Transactional + void assertThatNotActivatedUsersWithNotNullActivationKeyCreatedBefore3DaysAreDeleted() { + Instant now = Instant.now(); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); + user.setActivated(false); + user.setActivationKey(RandomStringUtils.random(20)); + User dbUser = userRepository.saveAndFlush(user); + dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); + userRepository.saveAndFlush(user); + Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); + List users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isNotEmpty(); + userService.removeNotActivatedUsers(); + users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isEmpty(); + } + + @Test + @Transactional + void assertThatNotActivatedUsersWithNullActivationKeyCreatedBefore3DaysAreNotDeleted() { + Instant now = Instant.now(); + when(dateTimeProvider.getNow()).thenReturn(Optional.of(now.minus(4, ChronoUnit.DAYS))); + user.setActivated(false); + User dbUser = userRepository.saveAndFlush(user); + dbUser.setCreatedDate(now.minus(4, ChronoUnit.DAYS)); + userRepository.saveAndFlush(user); + Instant threeDaysAgo = now.minus(3, ChronoUnit.DAYS); + List users = userRepository.findAllByActivatedIsFalseAndActivationKeyIsNotNullAndCreatedDateBefore(threeDaysAgo); + assertThat(users).isEmpty(); + userService.removeNotActivatedUsers(); + Optional maybeDbUser = userRepository.findById(dbUser.getId()); + assertThat(maybeDbUser).contains(dbUser); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/mapper/UserMapperTest.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/mapper/UserMapperTest.java new file mode 100644 index 0000000..d40360f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/service/mapper/UserMapperTest.java @@ -0,0 +1,132 @@ +package de.novatec.ittage.app.service.mapper; + +import static org.assertj.core.api.Assertions.assertThat; + +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.dto.UserDTO; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link UserMapper}. + */ +class UserMapperTest { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final Long DEFAULT_ID = 1L; + + private UserMapper userMapper; + private User user; + private AdminUserDTO userDto; + + @BeforeEach + public void init() { + userMapper = new UserMapper(); + user = new User(); + user.setLogin(DEFAULT_LOGIN); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + user.setEmail("johndoe@localhost"); + user.setFirstName("john"); + user.setLastName("doe"); + user.setImageUrl("image_url"); + user.setLangKey("en"); + + userDto = new AdminUserDTO(user); + } + + @Test + void usersToUserDTOsShouldMapOnlyNonNullUsers() { + List users = new ArrayList<>(); + users.add(user); + users.add(null); + + List userDTOS = userMapper.usersToUserDTOs(users); + + assertThat(userDTOS).isNotEmpty().size().isEqualTo(1); + } + + @Test + void userDTOsToUsersShouldMapOnlyNonNullUsers() { + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + usersDto.add(null); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty().size().isEqualTo(1); + } + + @Test + void userDTOsToUsersWithAuthoritiesStringShouldMapToUsersWithAuthoritiesDomain() { + Set authoritiesAsString = new HashSet<>(); + authoritiesAsString.add("ADMIN"); + userDto.setAuthorities(authoritiesAsString); + + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty().size().isEqualTo(1); + assertThat(users.get(0).getAuthorities()).isNotNull(); + assertThat(users.get(0).getAuthorities()).isNotEmpty(); + assertThat(users.get(0).getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); + } + + @Test + void userDTOsToUsersMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + List usersDto = new ArrayList<>(); + usersDto.add(userDto); + + List users = userMapper.userDTOsToUsers(usersDto); + + assertThat(users).isNotEmpty().size().isEqualTo(1); + assertThat(users.get(0).getAuthorities()).isNotNull(); + assertThat(users.get(0).getAuthorities()).isEmpty(); + } + + @Test + void userDTOToUserMapWithAuthoritiesStringShouldReturnUserWithAuthorities() { + Set authoritiesAsString = new HashSet<>(); + authoritiesAsString.add("ADMIN"); + userDto.setAuthorities(authoritiesAsString); + + User user = userMapper.userDTOToUser(userDto); + + assertThat(user).isNotNull(); + assertThat(user.getAuthorities()).isNotNull(); + assertThat(user.getAuthorities()).isNotEmpty(); + assertThat(user.getAuthorities().iterator().next().getName()).isEqualTo("ADMIN"); + } + + @Test + void userDTOToUserMapWithNullAuthoritiesStringShouldReturnUserWithEmptyAuthorities() { + userDto.setAuthorities(null); + + User user = userMapper.userDTOToUser(userDto); + + assertThat(user).isNotNull(); + assertThat(user.getAuthorities()).isNotNull(); + assertThat(user.getAuthorities()).isEmpty(); + } + + @Test + void userDTOToUserMapWithNullUserShouldReturnNull() { + assertThat(userMapper.userDTOToUser(null)).isNull(); + } + + @Test + void testUserFromId() { + assertThat(userMapper.userFromId(DEFAULT_ID).getId()).isEqualTo(DEFAULT_ID); + assertThat(userMapper.userFromId(null)).isNull(); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/filter/SpaWebFilterIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/filter/SpaWebFilterIT.java new file mode 100644 index 0000000..a364f6b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/filter/SpaWebFilterIT.java @@ -0,0 +1,88 @@ +package de.novatec.ittage.app.web.filter; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +@AutoConfigureMockMvc +@WithMockUser +@IntegrationTest +class SpaWebFilterIT { + + @Autowired + private MockMvc mockMvc; + + @Test + void testFilterForwardsToIndex() throws Exception { + mockMvc.perform(get("/")).andExpect(status().isOk()).andExpect(forwardedUrl("/index.html")); + } + + @Test + void testFilterDoesNotForwardToIndexForApi() throws Exception { + mockMvc.perform(get("/api/authenticate")).andExpect(status().isOk()).andExpect(forwardedUrl(null)); + } + + @Test + @WithMockUser(authorities = AuthoritiesConstants.ADMIN) + void testFilterDoesNotForwardToIndexForV3ApiDocs() throws Exception { + mockMvc.perform(get("/v3/api-docs")).andExpect(status().isOk()).andExpect(forwardedUrl(null)); + } + + @Test + void testFilterDoesNotForwardToIndexForDotFile() throws Exception { + mockMvc.perform(get("/file.js")).andExpect(status().isNotFound()); + } + + @Test + void getBackendEndpoint() throws Exception { + mockMvc.perform(get("/test")).andExpect(status().isOk()).andExpect(forwardedUrl("/index.html")); + } + + @Test + void forwardUnmappedFirstLevelMapping() throws Exception { + mockMvc.perform(get("/first-level")).andExpect(status().isOk()).andExpect(forwardedUrl("/index.html")); + } + + @Test + void forwardUnmappedSecondLevelMapping() throws Exception { + mockMvc.perform(get("/first-level/second-level")).andExpect(status().isOk()).andExpect(forwardedUrl("/index.html")); + } + + @Test + void forwardUnmappedThirdLevelMapping() throws Exception { + mockMvc.perform(get("/first-level/second-level/third-level")).andExpect(status().isOk()).andExpect(forwardedUrl("/index.html")); + } + + @Test + void forwardUnmappedDeepMapping() throws Exception { + mockMvc.perform(get("/1/2/3/4/5/6/7/8/9/10")).andExpect(forwardedUrl("/index.html")); + } + + @Test + void getUnmappedFirstLevelFile() throws Exception { + mockMvc.perform(get("/foo.js")).andExpect(status().isNotFound()); + } + + /** + * This test verifies that any files that aren't permitted by Spring Security will be forbidden. + * If you want to change this to return isNotFound(), you need to add a request mapping that + * allows this file in SecurityConfiguration. + */ + @Test + void getUnmappedSecondLevelFile() throws Exception { + mockMvc.perform(get("/foo/bar.js")).andExpect(status().isForbidden()); + } + + @Test + void getUnmappedThirdLevelFile() throws Exception { + mockMvc.perform(get("/foo/another/bar.js")).andExpect(status().isForbidden()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AccountResourceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AccountResourceIT.java new file mode 100644 index 0000000..0346313 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AccountResourceIT.java @@ -0,0 +1,752 @@ +package de.novatec.ittage.app.web.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.config.Constants; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.AuthorityRepository; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import de.novatec.ittage.app.service.UserService; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.dto.PasswordChangeDTO; +import de.novatec.ittage.app.web.rest.vm.KeyAndPasswordVM; +import de.novatec.ittage.app.web.rest.vm.ManagedUserVM; +import java.time.Instant; +import java.util.*; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link AccountResource} REST controller. + */ +@AutoConfigureMockMvc +@IntegrationTest +class AccountResourceIT { + + static final String TEST_USER_LOGIN = "test"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private AuthorityRepository authorityRepository; + + @Autowired + private UserService userService; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private MockMvc restAccountMockMvc; + + @Test + @WithUnauthenticatedMockUser + void testNonAuthenticatedUser() throws Exception { + restAccountMockMvc + .perform(get("/api/authenticate").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string("")); + } + + @Test + @WithMockUser(TEST_USER_LOGIN) + void testAuthenticatedUser() throws Exception { + restAccountMockMvc + .perform(get("/api/authenticate").with(request -> request).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().string(TEST_USER_LOGIN)); + } + + @Test + @WithMockUser(TEST_USER_LOGIN) + void testGetExistingAccount() throws Exception { + Set authorities = new HashSet<>(); + authorities.add(AuthoritiesConstants.ADMIN); + + AdminUserDTO user = new AdminUserDTO(); + user.setLogin(TEST_USER_LOGIN); + user.setFirstName("john"); + user.setLastName("doe"); + user.setEmail("john.doe@jhipster.com"); + user.setImageUrl("http://placehold.it/50x50"); + user.setLangKey("en"); + user.setAuthorities(authorities); + userService.createUser(user); + + restAccountMockMvc + .perform(get("/api/account").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.login").value(TEST_USER_LOGIN)) + .andExpect(jsonPath("$.firstName").value("john")) + .andExpect(jsonPath("$.lastName").value("doe")) + .andExpect(jsonPath("$.email").value("john.doe@jhipster.com")) + .andExpect(jsonPath("$.imageUrl").value("http://placehold.it/50x50")) + .andExpect(jsonPath("$.langKey").value("en")) + .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN)); + } + + @Test + void testGetUnknownAccount() throws Exception { + restAccountMockMvc.perform(get("/api/account").accept(MediaType.APPLICATION_PROBLEM_JSON)).andExpect(status().isUnauthorized()); + } + + @Test + @Transactional + void testRegisterValid() throws Exception { + ManagedUserVM validUser = new ManagedUserVM(); + validUser.setLogin("test-register-valid"); + validUser.setPassword("password"); + validUser.setFirstName("Alice"); + validUser.setLastName("Test"); + validUser.setEmail("test-register-valid@example.com"); + validUser.setImageUrl("http://placehold.it/50x50"); + validUser.setLangKey(Constants.DEFAULT_LANGUAGE); + validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + assertThat(userRepository.findOneByLogin("test-register-valid")).isEmpty(); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + assertThat(userRepository.findOneByLogin("test-register-valid")).isPresent(); + } + + @Test + @Transactional + void testRegisterInvalidLogin() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("funky-log(n"); // <-- invalid + invalidUser.setPassword("password"); + invalidUser.setFirstName("Funky"); + invalidUser.setLastName("One"); + invalidUser.setEmail("funky@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByEmailIgnoreCase("funky@example.com"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterInvalidEmail() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword("password"); + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("invalid"); // <-- invalid + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterInvalidPassword() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword("123"); // password with only 3 digits + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("bob@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterNullPassword() throws Exception { + ManagedUserVM invalidUser = new ManagedUserVM(); + invalidUser.setLogin("bob"); + invalidUser.setPassword(null); // invalid null password + invalidUser.setFirstName("Bob"); + invalidUser.setLastName("Green"); + invalidUser.setEmail("bob@example.com"); + invalidUser.setActivated(true); + invalidUser.setImageUrl("http://placehold.it/50x50"); + invalidUser.setLangKey(Constants.DEFAULT_LANGUAGE); + invalidUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(invalidUser))) + .andExpect(status().isBadRequest()); + + Optional user = userRepository.findOneByLogin("bob"); + assertThat(user).isEmpty(); + } + + @Test + @Transactional + void testRegisterDuplicateLogin() throws Exception { + // First registration + ManagedUserVM firstUser = new ManagedUserVM(); + firstUser.setLogin("alice"); + firstUser.setPassword("password"); + firstUser.setFirstName("Alice"); + firstUser.setLastName("Something"); + firstUser.setEmail("alice@example.com"); + firstUser.setImageUrl("http://placehold.it/50x50"); + firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); + firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Duplicate login, different email + ManagedUserVM secondUser = new ManagedUserVM(); + secondUser.setLogin(firstUser.getLogin()); + secondUser.setPassword(firstUser.getPassword()); + secondUser.setFirstName(firstUser.getFirstName()); + secondUser.setLastName(firstUser.getLastName()); + secondUser.setEmail("alice2@example.com"); + secondUser.setImageUrl(firstUser.getImageUrl()); + secondUser.setLangKey(firstUser.getLangKey()); + secondUser.setCreatedBy(firstUser.getCreatedBy()); + secondUser.setCreatedDate(firstUser.getCreatedDate()); + secondUser.setLastModifiedBy(firstUser.getLastModifiedBy()); + secondUser.setLastModifiedDate(firstUser.getLastModifiedDate()); + secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // First user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) + .andExpect(status().isCreated()); + + // Second (non activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().isCreated()); + + Optional testUser = userRepository.findOneByEmailIgnoreCase("alice2@example.com"); + assertThat(testUser).isPresent(); + testUser.orElseThrow().setActivated(true); + userRepository.save(testUser.orElseThrow()); + + // Second (already activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().is4xxClientError()); + } + + @Test + @Transactional + void testRegisterDuplicateEmail() throws Exception { + // First user + ManagedUserVM firstUser = new ManagedUserVM(); + firstUser.setLogin("test-register-duplicate-email"); + firstUser.setPassword("password"); + firstUser.setFirstName("Alice"); + firstUser.setLastName("Test"); + firstUser.setEmail("test-register-duplicate-email@example.com"); + firstUser.setImageUrl("http://placehold.it/50x50"); + firstUser.setLangKey(Constants.DEFAULT_LANGUAGE); + firstUser.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Register first user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(firstUser))) + .andExpect(status().isCreated()); + + Optional testUser1 = userRepository.findOneByLogin("test-register-duplicate-email"); + assertThat(testUser1).isPresent(); + + // Duplicate email, different login + ManagedUserVM secondUser = new ManagedUserVM(); + secondUser.setLogin("test-register-duplicate-email-2"); + secondUser.setPassword(firstUser.getPassword()); + secondUser.setFirstName(firstUser.getFirstName()); + secondUser.setLastName(firstUser.getLastName()); + secondUser.setEmail(firstUser.getEmail()); + secondUser.setImageUrl(firstUser.getImageUrl()); + secondUser.setLangKey(firstUser.getLangKey()); + secondUser.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // Register second (non activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().isCreated()); + + Optional testUser2 = userRepository.findOneByLogin("test-register-duplicate-email"); + assertThat(testUser2).isEmpty(); + + Optional testUser3 = userRepository.findOneByLogin("test-register-duplicate-email-2"); + assertThat(testUser3).isPresent(); + + // Duplicate email - with uppercase email address + ManagedUserVM userWithUpperCaseEmail = new ManagedUserVM(); + userWithUpperCaseEmail.setId(firstUser.getId()); + userWithUpperCaseEmail.setLogin("test-register-duplicate-email-3"); + userWithUpperCaseEmail.setPassword(firstUser.getPassword()); + userWithUpperCaseEmail.setFirstName(firstUser.getFirstName()); + userWithUpperCaseEmail.setLastName(firstUser.getLastName()); + userWithUpperCaseEmail.setEmail("TEST-register-duplicate-email@example.com"); + userWithUpperCaseEmail.setImageUrl(firstUser.getImageUrl()); + userWithUpperCaseEmail.setLangKey(firstUser.getLangKey()); + userWithUpperCaseEmail.setAuthorities(new HashSet<>(firstUser.getAuthorities())); + + // Register third (not activated) user + restAccountMockMvc + .perform( + post("/api/register") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(userWithUpperCaseEmail)) + ) + .andExpect(status().isCreated()); + + Optional testUser4 = userRepository.findOneByLogin("test-register-duplicate-email-3"); + assertThat(testUser4).isPresent(); + assertThat(testUser4.orElseThrow().getEmail()).isEqualTo("test-register-duplicate-email@example.com"); + + testUser4.orElseThrow().setActivated(true); + userService.updateUser((new AdminUserDTO(testUser4.orElseThrow()))); + + // Register 4th (already activated) user + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(secondUser))) + .andExpect(status().is4xxClientError()); + } + + @Test + @Transactional + void testRegisterAdminIsIgnored() throws Exception { + ManagedUserVM validUser = new ManagedUserVM(); + validUser.setLogin("badguy"); + validUser.setPassword("password"); + validUser.setFirstName("Bad"); + validUser.setLastName("Guy"); + validUser.setEmail("badguy@example.com"); + validUser.setActivated(true); + validUser.setImageUrl("http://placehold.it/50x50"); + validUser.setLangKey(Constants.DEFAULT_LANGUAGE); + validUser.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/register").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(validUser))) + .andExpect(status().isCreated()); + + Optional userDup = userRepository.findOneWithAuthoritiesByLogin("badguy"); + assertThat(userDup).isPresent(); + assertThat(userDup.orElseThrow().getAuthorities()) + .hasSize(1) + .containsExactly(authorityRepository.findById(AuthoritiesConstants.USER).orElseThrow()); + } + + @Test + @Transactional + void testActivateAccount() throws Exception { + final String activationKey = "some activation key"; + User user = new User(); + user.setLogin("activate-account"); + user.setEmail("activate-account@example.com"); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(false); + user.setActivationKey(activationKey); + + userRepository.saveAndFlush(user); + + restAccountMockMvc.perform(get("/api/activate?key={activationKey}", activationKey)).andExpect(status().isOk()); + + user = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(user.isActivated()).isTrue(); + } + + @Test + @Transactional + void testActivateAccountWithWrongKey() throws Exception { + restAccountMockMvc.perform(get("/api/activate?key=wrongActivationKey")).andExpect(status().isInternalServerError()); + } + + @Test + @Transactional + @WithMockUser("save-account") + void testSaveAccount() throws Exception { + User user = new User(); + user.setLogin("save-account"); + user.setEmail("save-account@example.com"); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-account@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneWithAuthoritiesByLogin(user.getLogin()).orElse(null); + assertThat(updatedUser.getFirstName()).isEqualTo(userDTO.getFirstName()); + assertThat(updatedUser.getLastName()).isEqualTo(userDTO.getLastName()); + assertThat(updatedUser.getEmail()).isEqualTo(userDTO.getEmail()); + assertThat(updatedUser.getLangKey()).isEqualTo(userDTO.getLangKey()); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + assertThat(updatedUser.getImageUrl()).isEqualTo(userDTO.getImageUrl()); + assertThat(updatedUser.isActivated()).isTrue(); + assertThat(updatedUser.getAuthorities()).isEmpty(); + } + + @Test + @Transactional + @WithMockUser("save-invalid-email") + void testSaveInvalidEmail() throws Exception { + User user = new User(); + user.setLogin("save-invalid-email"); + user.setEmail("save-invalid-email@example.com"); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("invalid email"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isBadRequest()); + + assertThat(userRepository.findOneByEmailIgnoreCase("invalid email")).isNotPresent(); + } + + @Test + @Transactional + @WithMockUser("save-existing-email") + void testSaveExistingEmail() throws Exception { + User user = new User(); + user.setLogin("save-existing-email"); + user.setEmail("save-existing-email@example.com"); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("save-existing-email2"); + anotherUser.setEmail("save-existing-email2@example.com"); + anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); + anotherUser.setActivated(true); + + userRepository.saveAndFlush(anotherUser); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-existing-email2@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("save-existing-email").orElse(null); + assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email@example.com"); + } + + @Test + @Transactional + @WithMockUser("save-existing-email-and-login") + void testSaveExistingEmailAndLogin() throws Exception { + User user = new User(); + user.setLogin("save-existing-email-and-login"); + user.setEmail("save-existing-email-and-login@example.com"); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + userRepository.saveAndFlush(user); + + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setLogin("not-used"); + userDTO.setFirstName("firstname"); + userDTO.setLastName("lastname"); + userDTO.setEmail("save-existing-email-and-login@example.com"); + userDTO.setActivated(false); + userDTO.setImageUrl("http://placehold.it/50x50"); + userDTO.setLangKey(Constants.DEFAULT_LANGUAGE); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.ADMIN)); + + restAccountMockMvc + .perform(post("/api/account").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(userDTO))) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin("save-existing-email-and-login").orElse(null); + assertThat(updatedUser.getEmail()).isEqualTo("save-existing-email-and-login@example.com"); + } + + @Test + @Transactional + @WithMockUser("change-password-wrong-existing-password") + void testChangePasswordWrongExistingPassword() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.randomAlphanumeric(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-wrong-existing-password"); + user.setEmail("change-password-wrong-existing-password@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO("1" + currentPassword, "new password"))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-wrong-existing-password").orElse(null); + assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isFalse(); + assertThat(passwordEncoder.matches(currentPassword, updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + @WithMockUser("change-password") + void testChangePassword() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.randomAlphanumeric(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password"); + user.setEmail("change-password@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, "new password"))) + ) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin("change-password").orElse(null); + assertThat(passwordEncoder.matches("new password", updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + @WithMockUser("change-password-too-small") + void testChangePasswordTooSmall() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.randomAlphanumeric(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-too-small"); + user.setEmail("change-password-too-small@example.com"); + userRepository.saveAndFlush(user); + + String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MIN_LENGTH - 1); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-too-small").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + @WithMockUser("change-password-too-long") + void testChangePasswordTooLong() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.randomAlphanumeric(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-too-long"); + user.setEmail("change-password-too-long@example.com"); + userRepository.saveAndFlush(user); + + String newPassword = RandomStringUtils.random(ManagedUserVM.PASSWORD_MAX_LENGTH + 1); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, newPassword))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-too-long").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + @WithMockUser("change-password-empty") + void testChangePasswordEmpty() throws Exception { + User user = new User(); + String currentPassword = RandomStringUtils.randomAlphanumeric(60); + user.setPassword(passwordEncoder.encode(currentPassword)); + user.setLogin("change-password-empty"); + user.setEmail("change-password-empty@example.com"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform( + post("/api/account/change-password") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(new PasswordChangeDTO(currentPassword, ""))) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin("change-password-empty").orElse(null); + assertThat(updatedUser.getPassword()).isEqualTo(user.getPassword()); + } + + @Test + @Transactional + void testRequestPasswordReset() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + user.setLogin("password-reset"); + user.setEmail("password-reset@example.com"); + user.setLangKey("en"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset@example.com")) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void testRequestPasswordResetUpperCaseEmail() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + user.setLogin("password-reset-upper-case"); + user.setEmail("password-reset-upper-case@example.com"); + user.setLangKey("en"); + userRepository.saveAndFlush(user); + + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset-upper-case@EXAMPLE.COM")) + .andExpect(status().isOk()); + } + + @Test + void testRequestPasswordResetWrongEmail() throws Exception { + restAccountMockMvc + .perform(post("/api/account/reset-password/init").content("password-reset-wrong-email@example.com")) + .andExpect(status().isOk()); + } + + @Test + @Transactional + void testFinishPasswordReset() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setLogin("finish-password-reset"); + user.setEmail("finish-password-reset@example.com"); + user.setResetDate(Instant.now().plusSeconds(60)); + user.setResetKey("reset key"); + userRepository.saveAndFlush(user); + + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey(user.getResetKey()); + keyAndPassword.setNewPassword("new password"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isOk()); + + User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isTrue(); + } + + @Test + @Transactional + void testFinishPasswordResetTooSmall() throws Exception { + User user = new User(); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setLogin("finish-password-reset-too-small"); + user.setEmail("finish-password-reset-too-small@example.com"); + user.setResetDate(Instant.now().plusSeconds(60)); + user.setResetKey("reset key too small"); + userRepository.saveAndFlush(user); + + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey(user.getResetKey()); + keyAndPassword.setNewPassword("foo"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isBadRequest()); + + User updatedUser = userRepository.findOneByLogin(user.getLogin()).orElse(null); + assertThat(passwordEncoder.matches(keyAndPassword.getNewPassword(), updatedUser.getPassword())).isFalse(); + } + + @Test + @Transactional + void testFinishPasswordResetWrongKey() throws Exception { + KeyAndPasswordVM keyAndPassword = new KeyAndPasswordVM(); + keyAndPassword.setKey("wrong reset key"); + keyAndPassword.setNewPassword("new password"); + + restAccountMockMvc + .perform( + post("/api/account/reset-password/finish") + .contentType(MediaType.APPLICATION_JSON) + .content(TestUtil.convertObjectToJsonBytes(keyAndPassword)) + ) + .andExpect(status().isInternalServerError()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AuthenticateControllerIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AuthenticateControllerIT.java new file mode 100644 index 0000000..5c33b0d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/AuthenticateControllerIT.java @@ -0,0 +1,98 @@ +package de.novatec.ittage.app.web.rest; + +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.web.rest.vm.LoginVM; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link AuthenticateController} REST controller. + */ +@AutoConfigureMockMvc +@IntegrationTest +class AuthenticateControllerIT { + + @Autowired + private UserRepository userRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Autowired + private MockMvc mockMvc; + + @Test + @Transactional + void testAuthorize() throws Exception { + User user = new User(); + user.setLogin("user-jwt-controller"); + user.setEmail("user-jwt-controller@example.com"); + user.setActivated(true); + user.setPassword(passwordEncoder.encode("test")); + + userRepository.saveAndFlush(user); + + LoginVM login = new LoginVM(); + login.setUsername("user-jwt-controller"); + login.setPassword("test"); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id_token").isString()) + .andExpect(jsonPath("$.id_token").isNotEmpty()) + .andExpect(header().string("Authorization", not(nullValue()))) + .andExpect(header().string("Authorization", not(is(emptyString())))); + } + + @Test + @Transactional + void testAuthorizeWithRememberMe() throws Exception { + User user = new User(); + user.setLogin("user-jwt-controller-remember-me"); + user.setEmail("user-jwt-controller-remember-me@example.com"); + user.setActivated(true); + user.setPassword(passwordEncoder.encode("test")); + + userRepository.saveAndFlush(user); + + LoginVM login = new LoginVM(); + login.setUsername("user-jwt-controller-remember-me"); + login.setPassword("test"); + login.setRememberMe(true); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.id_token").isString()) + .andExpect(jsonPath("$.id_token").isNotEmpty()) + .andExpect(header().string("Authorization", not(nullValue()))) + .andExpect(header().string("Authorization", not(is(emptyString())))); + } + + @Test + void testAuthorizeFails() throws Exception { + LoginVM login = new LoginVM(); + login.setUsername("wrong-user"); + login.setPassword("wrong password"); + mockMvc + .perform(post("/api/authenticate").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(login))) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.id_token").doesNotExist()) + .andExpect(header().doesNotExist("Authorization")); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/PublicUserResourceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/PublicUserResourceIT.java new file mode 100644 index 0000000..81598e1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/PublicUserResourceIT.java @@ -0,0 +1,99 @@ +package de.novatec.ittage.app.web.rest; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.cache.CacheManager; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link PublicUserResource} REST controller. + */ +@AutoConfigureMockMvc +@WithMockUser(authorities = AuthoritiesConstants.ADMIN) +@IntegrationTest +class PublicUserResourceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private EntityManager em; + + @Autowired + private CacheManager cacheManager; + + @Autowired + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void setup() { + cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); + cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); + } + + @BeforeEach + public void initTest() { + user = UserResourceIT.initTestUser(userRepository, em); + } + + @Test + @Transactional + void getAllPublicUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc + .perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) + .andExpect(jsonPath("$.[*].email").doesNotExist()) + .andExpect(jsonPath("$.[*].imageUrl").doesNotExist()) + .andExpect(jsonPath("$.[*].langKey").doesNotExist()); + } + + @Test + @Transactional + void getAllAuthorities() throws Exception { + restUserMockMvc + .perform(get("/api/authorities").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$").value(hasItems(AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN))); + } + + @Test + @Transactional + void getAllUsersSortedByParameters() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + restUserMockMvc.perform(get("/api/users?sort=resetKey,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); + restUserMockMvc.perform(get("/api/users?sort=password,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isBadRequest()); + restUserMockMvc + .perform(get("/api/users?sort=resetKey,desc&sort=id,desc").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()); + restUserMockMvc.perform(get("/api/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/TestUtil.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/TestUtil.java new file mode 100644 index 0000000..9cb73b4 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/TestUtil.java @@ -0,0 +1,206 @@ +package de.novatec.ittage.app.web.rest; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.persistence.EntityManager; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import java.io.IOException; +import java.math.BigDecimal; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.List; +import org.hamcrest.Description; +import org.hamcrest.TypeSafeDiagnosingMatcher; +import org.hamcrest.TypeSafeMatcher; +import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar; +import org.springframework.format.support.DefaultFormattingConversionService; +import org.springframework.format.support.FormattingConversionService; + +/** + * Utility class for testing REST controllers. + */ +public final class TestUtil { + + private static final ObjectMapper mapper = createObjectMapper(); + + private static ObjectMapper createObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(SerializationFeature.WRITE_DURATIONS_AS_TIMESTAMPS, false); + mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); + mapper.registerModule(new JavaTimeModule()); + return mapper; + } + + /** + * Convert an object to JSON byte array. + * + * @param object the object to convert. + * @return the JSON byte array. + * @throws IOException + */ + public static byte[] convertObjectToJsonBytes(Object object) throws IOException { + return mapper.writeValueAsBytes(object); + } + + /** + * Create a byte array with a specific size filled with specified data. + * + * @param size the size of the byte array. + * @param data the data to put in the byte array. + * @return the JSON byte array. + */ + public static byte[] createByteArray(int size, String data) { + byte[] byteArray = new byte[size]; + for (int i = 0; i < size; i++) { + byteArray[i] = Byte.parseByte(data, 2); + } + return byteArray; + } + + /** + * A matcher that tests that the examined string represents the same instant as the reference datetime. + */ + public static class ZonedDateTimeMatcher extends TypeSafeDiagnosingMatcher { + + private final ZonedDateTime date; + + public ZonedDateTimeMatcher(ZonedDateTime date) { + this.date = date; + } + + @Override + protected boolean matchesSafely(String item, Description mismatchDescription) { + try { + if (!date.isEqual(ZonedDateTime.parse(item))) { + mismatchDescription.appendText("was ").appendValue(item); + return false; + } + return true; + } catch (DateTimeParseException e) { + mismatchDescription.appendText("was ").appendValue(item).appendText(", which could not be parsed as a ZonedDateTime"); + return false; + } + } + + @Override + public void describeTo(Description description) { + description.appendText("a String representing the same Instant as ").appendValue(date); + } + } + + /** + * Creates a matcher that matches when the examined string represents the same instant as the reference datetime. + * + * @param date the reference datetime against which the examined string is checked. + */ + public static ZonedDateTimeMatcher sameInstant(ZonedDateTime date) { + return new ZonedDateTimeMatcher(date); + } + + /** + * A matcher that tests that the examined number represents the same value - it can be Long, Double, etc - as the reference BigDecimal. + */ + public static class NumberMatcher extends TypeSafeMatcher { + + final BigDecimal value; + + public NumberMatcher(BigDecimal value) { + this.value = value; + } + + @Override + public void describeTo(Description description) { + description.appendText("a numeric value is ").appendValue(value); + } + + @Override + protected boolean matchesSafely(Number item) { + BigDecimal bigDecimal = asDecimal(item); + return bigDecimal != null && value.compareTo(bigDecimal) == 0; + } + + private static BigDecimal asDecimal(Number item) { + if (item == null) { + return null; + } + if (item instanceof BigDecimal) { + return (BigDecimal) item; + } else if (item instanceof Long) { + return BigDecimal.valueOf((Long) item); + } else if (item instanceof Integer) { + return BigDecimal.valueOf((Integer) item); + } else if (item instanceof Double) { + return BigDecimal.valueOf((Double) item); + } else if (item instanceof Float) { + return BigDecimal.valueOf((Float) item); + } else { + return BigDecimal.valueOf(item.doubleValue()); + } + } + } + + /** + * Creates a matcher that matches when the examined number represents the same value as the reference BigDecimal. + * + * @param number the reference BigDecimal against which the examined number is checked. + */ + public static NumberMatcher sameNumber(BigDecimal number) { + return new NumberMatcher(number); + } + + /** + * Verifies the equals/hashcode contract on the domain object. + */ + public static void equalsVerifier(Class clazz) throws Exception { + T domainObject1 = clazz.getConstructor().newInstance(); + assertThat(domainObject1.toString()).isNotNull(); + assertThat(domainObject1).isEqualTo(domainObject1); + assertThat(domainObject1).hasSameHashCodeAs(domainObject1); + // Test with an instance of another class + Object testOtherObject = new Object(); + assertThat(domainObject1).isNotEqualTo(testOtherObject); + assertThat(domainObject1).isNotEqualTo(null); + // Test with an instance of the same class + T domainObject2 = clazz.getConstructor().newInstance(); + assertThat(domainObject1).isNotEqualTo(domainObject2); + // HashCodes are equals because the objects are not persisted yet + assertThat(domainObject1).hasSameHashCodeAs(domainObject2); + } + + /** + * Create a {@link FormattingConversionService} which use ISO date format, instead of the localized one. + * @return the {@link FormattingConversionService}. + */ + public static FormattingConversionService createFormattingConversionService() { + DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService(); + DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar(); + registrar.setUseIsoFormat(true); + registrar.registerFormatters(dfcs); + return dfcs; + } + + /** + * Executes a query on the EntityManager finding all stored objects. + * @param The type of objects to be searched + * @param em The instance of the EntityManager + * @param clazz The class type to be searched + * @return A list of all found objects + */ + public static List findAll(EntityManager em, Class clazz) { + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery cq = cb.createQuery(clazz); + Root rootEntry = cq.from(clazz); + CriteriaQuery all = cq.select(rootEntry); + TypedQuery allQuery = em.createQuery(all); + return allQuery.getResultList(); + } + + private TestUtil() {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/UserResourceIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/UserResourceIT.java new file mode 100644 index 0000000..884d560 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/UserResourceIT.java @@ -0,0 +1,557 @@ +package de.novatec.ittage.app.web.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.hasItem; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import de.novatec.ittage.app.IntegrationTest; +import de.novatec.ittage.app.domain.Authority; +import de.novatec.ittage.app.domain.User; +import de.novatec.ittage.app.repository.UserRepository; +import de.novatec.ittage.app.security.AuthoritiesConstants; +import de.novatec.ittage.app.service.dto.AdminUserDTO; +import de.novatec.ittage.app.service.mapper.UserMapper; +import jakarta.persistence.EntityManager; +import java.time.Instant; +import java.util.*; +import java.util.function.Consumer; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.cache.CacheManager; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for the {@link UserResource} REST controller. + */ +@AutoConfigureMockMvc +@WithMockUser(authorities = AuthoritiesConstants.ADMIN) +@IntegrationTest +class UserResourceIT { + + private static final String DEFAULT_LOGIN = "johndoe"; + private static final String UPDATED_LOGIN = "jhipster"; + + private static final Long DEFAULT_ID = 1L; + + private static final String DEFAULT_PASSWORD = "passjohndoe"; + private static final String UPDATED_PASSWORD = "passjhipster"; + + private static final String DEFAULT_EMAIL = "johndoe@localhost"; + private static final String UPDATED_EMAIL = "jhipster@localhost"; + + private static final String DEFAULT_FIRSTNAME = "john"; + private static final String UPDATED_FIRSTNAME = "jhipsterFirstName"; + + private static final String DEFAULT_LASTNAME = "doe"; + private static final String UPDATED_LASTNAME = "jhipsterLastName"; + + private static final String DEFAULT_IMAGEURL = "http://placehold.it/50x50"; + private static final String UPDATED_IMAGEURL = "http://placehold.it/40x40"; + + private static final String DEFAULT_LANGKEY = "en"; + private static final String UPDATED_LANGKEY = "fr"; + + @Autowired + private UserRepository userRepository; + + @Autowired + private UserMapper userMapper; + + @Autowired + private EntityManager em; + + @Autowired + private CacheManager cacheManager; + + @Autowired + private MockMvc restUserMockMvc; + + private User user; + + @BeforeEach + public void setup() { + cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).clear(); + cacheManager.getCache(UserRepository.USERS_BY_EMAIL_CACHE).clear(); + } + + /** + * Create a User. + * + * This is a static method, as tests for other entities might also need it, + * if they test an entity which has a required relationship to the User entity. + */ + public static User createEntity(EntityManager em) { + User user = new User(); + user.setLogin(DEFAULT_LOGIN + RandomStringUtils.randomAlphabetic(5)); + user.setPassword(RandomStringUtils.randomAlphanumeric(60)); + user.setActivated(true); + user.setEmail(RandomStringUtils.randomAlphabetic(5) + DEFAULT_EMAIL); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + return user; + } + + /** + * Setups the database with one user. + */ + public static User initTestUser(UserRepository userRepository, EntityManager em) { + userRepository.deleteAll(); + User user = createEntity(em); + user.setLogin(DEFAULT_LOGIN); + user.setEmail(DEFAULT_EMAIL); + return user; + } + + @BeforeEach + public void initTest() { + user = initTestUser(userRepository, em); + } + + @Test + @Transactional + void createUser() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + // Create the User + AdminUserDTO user = new AdminUserDTO(); + user.setLogin(DEFAULT_LOGIN); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setEmail(DEFAULT_EMAIL); + user.setActivated(true); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform(post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isCreated()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeCreate + 1); + User testUser = users.get(users.size() - 1); + assertThat(testUser.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + }); + } + + @Test + @Transactional + void createUserWithExistingId() throws Exception { + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + AdminUserDTO user = new AdminUserDTO(); + user.setId(DEFAULT_ID); + user.setLogin(DEFAULT_LOGIN); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setEmail(DEFAULT_EMAIL); + user.setActivated(true); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // An entity with an existing ID cannot be created, so this API call must fail + restUserMockMvc + .perform(post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void createUserWithExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + AdminUserDTO user = new AdminUserDTO(); + user.setLogin(DEFAULT_LOGIN); // this login should already be used + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setEmail("anothermail@localhost"); + user.setActivated(true); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Create the User + restUserMockMvc + .perform(post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void createUserWithExistingEmail() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeCreate = userRepository.findAll().size(); + + AdminUserDTO user = new AdminUserDTO(); + user.setLogin("anotherlogin"); + user.setFirstName(DEFAULT_FIRSTNAME); + user.setLastName(DEFAULT_LASTNAME); + user.setEmail(DEFAULT_EMAIL); // this email should already be used + user.setActivated(true); + user.setImageUrl(DEFAULT_IMAGEURL); + user.setLangKey(DEFAULT_LANGKEY); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + // Create the User + restUserMockMvc + .perform(post("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isBadRequest()); + + // Validate the User in the database + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeCreate)); + } + + @Test + @Transactional + void getAllUsers() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + // Get all the users + restUserMockMvc + .perform(get("/api/admin/users?sort=id,desc").accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.[*].login").value(hasItem(DEFAULT_LOGIN))) + .andExpect(jsonPath("$.[*].firstName").value(hasItem(DEFAULT_FIRSTNAME))) + .andExpect(jsonPath("$.[*].lastName").value(hasItem(DEFAULT_LASTNAME))) + .andExpect(jsonPath("$.[*].email").value(hasItem(DEFAULT_EMAIL))) + .andExpect(jsonPath("$.[*].imageUrl").value(hasItem(DEFAULT_IMAGEURL))) + .andExpect(jsonPath("$.[*].langKey").value(hasItem(DEFAULT_LANGKEY))); + } + + @Test + @Transactional + void getUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); + + // Get the user + restUserMockMvc + .perform(get("/api/admin/users/{login}", user.getLogin())) + .andExpect(status().isOk()) + .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("$.login").value(user.getLogin())) + .andExpect(jsonPath("$.firstName").value(DEFAULT_FIRSTNAME)) + .andExpect(jsonPath("$.lastName").value(DEFAULT_LASTNAME)) + .andExpect(jsonPath("$.email").value(DEFAULT_EMAIL)) + .andExpect(jsonPath("$.imageUrl").value(DEFAULT_IMAGEURL)) + .andExpect(jsonPath("$.langKey").value(DEFAULT_LANGKEY)); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNotNull(); + } + + @Test + @Transactional + void getNonExistingUser() throws Exception { + restUserMockMvc.perform(get("/api/admin/users/unknown")).andExpect(status().isNotFound()); + } + + @Test + @Transactional + void updateUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).orElseThrow(); + + AdminUserDTO user = new AdminUserDTO(); + user.setId(updatedUser.getId()); + user.setLogin(updatedUser.getLogin()); + user.setFirstName(UPDATED_FIRSTNAME); + user.setLastName(UPDATED_LASTNAME); + user.setEmail(UPDATED_EMAIL); + user.setActivated(updatedUser.isActivated()); + user.setImageUrl(UPDATED_IMAGEURL); + user.setLangKey(UPDATED_LANGKEY); + user.setCreatedBy(updatedUser.getCreatedBy()); + user.setCreatedDate(updatedUser.getCreatedDate()); + user.setLastModifiedBy(updatedUser.getLastModifiedBy()); + user.setLastModifiedDate(updatedUser.getLastModifiedDate()); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform(put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isOk()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeUpdate); + User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().orElseThrow(); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + }); + } + + @Test + @Transactional + void updateUserLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeUpdate = userRepository.findAll().size(); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).orElseThrow(); + + AdminUserDTO user = new AdminUserDTO(); + user.setId(updatedUser.getId()); + user.setLogin(UPDATED_LOGIN); + user.setFirstName(UPDATED_FIRSTNAME); + user.setLastName(UPDATED_LASTNAME); + user.setEmail(UPDATED_EMAIL); + user.setActivated(updatedUser.isActivated()); + user.setImageUrl(UPDATED_IMAGEURL); + user.setLangKey(UPDATED_LANGKEY); + user.setCreatedBy(updatedUser.getCreatedBy()); + user.setCreatedDate(updatedUser.getCreatedDate()); + user.setLastModifiedBy(updatedUser.getLastModifiedBy()); + user.setLastModifiedDate(updatedUser.getLastModifiedDate()); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform(put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isOk()); + + // Validate the User in the database + assertPersistedUsers(users -> { + assertThat(users).hasSize(databaseSizeBeforeUpdate); + User testUser = users.stream().filter(usr -> usr.getId().equals(updatedUser.getId())).findFirst().orElseThrow(); + assertThat(testUser.getLogin()).isEqualTo(UPDATED_LOGIN); + assertThat(testUser.getFirstName()).isEqualTo(UPDATED_FIRSTNAME); + assertThat(testUser.getLastName()).isEqualTo(UPDATED_LASTNAME); + assertThat(testUser.getEmail()).isEqualTo(UPDATED_EMAIL); + assertThat(testUser.getImageUrl()).isEqualTo(UPDATED_IMAGEURL); + assertThat(testUser.getLangKey()).isEqualTo(UPDATED_LANGKEY); + }); + } + + @Test + @Transactional + void updateUserExistingEmail() throws Exception { + // Initialize the database with 2 users + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).orElseThrow(); + + AdminUserDTO user = new AdminUserDTO(); + user.setId(updatedUser.getId()); + user.setLogin(updatedUser.getLogin()); + user.setFirstName(updatedUser.getFirstName()); + user.setLastName(updatedUser.getLastName()); + user.setEmail("jhipster@localhost"); // this email should already be used by anotherUser + user.setActivated(updatedUser.isActivated()); + user.setImageUrl(updatedUser.getImageUrl()); + user.setLangKey(updatedUser.getLangKey()); + user.setCreatedBy(updatedUser.getCreatedBy()); + user.setCreatedDate(updatedUser.getCreatedDate()); + user.setLastModifiedBy(updatedUser.getLastModifiedBy()); + user.setLastModifiedDate(updatedUser.getLastModifiedDate()); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform(put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + void updateUserExistingLogin() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + + User anotherUser = new User(); + anotherUser.setLogin("jhipster"); + anotherUser.setPassword(RandomStringUtils.randomAlphanumeric(60)); + anotherUser.setActivated(true); + anotherUser.setEmail("jhipster@localhost"); + anotherUser.setFirstName("java"); + anotherUser.setLastName("hipster"); + anotherUser.setImageUrl(""); + anotherUser.setLangKey("en"); + userRepository.saveAndFlush(anotherUser); + + // Update the user + User updatedUser = userRepository.findById(user.getId()).orElseThrow(); + + AdminUserDTO user = new AdminUserDTO(); + user.setId(updatedUser.getId()); + user.setLogin("jhipster"); // this login should already be used by anotherUser + user.setFirstName(updatedUser.getFirstName()); + user.setLastName(updatedUser.getLastName()); + user.setEmail(updatedUser.getEmail()); + user.setActivated(updatedUser.isActivated()); + user.setImageUrl(updatedUser.getImageUrl()); + user.setLangKey(updatedUser.getLangKey()); + user.setCreatedBy(updatedUser.getCreatedBy()); + user.setCreatedDate(updatedUser.getCreatedDate()); + user.setLastModifiedBy(updatedUser.getLastModifiedBy()); + user.setLastModifiedDate(updatedUser.getLastModifiedDate()); + user.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + restUserMockMvc + .perform(put("/api/admin/users").contentType(MediaType.APPLICATION_JSON).content(TestUtil.convertObjectToJsonBytes(user))) + .andExpect(status().isBadRequest()); + } + + @Test + @Transactional + void deleteUser() throws Exception { + // Initialize the database + userRepository.saveAndFlush(user); + int databaseSizeBeforeDelete = userRepository.findAll().size(); + + // Delete the user + restUserMockMvc + .perform(delete("/api/admin/users/{login}", user.getLogin()).accept(MediaType.APPLICATION_JSON)) + .andExpect(status().isNoContent()); + + assertThat(cacheManager.getCache(UserRepository.USERS_BY_LOGIN_CACHE).get(user.getLogin())).isNull(); + + // Validate the database is empty + assertPersistedUsers(users -> assertThat(users).hasSize(databaseSizeBeforeDelete - 1)); + } + + @Test + void testUserEquals() throws Exception { + TestUtil.equalsVerifier(User.class); + User user1 = new User(); + user1.setId(DEFAULT_ID); + User user2 = new User(); + user2.setId(user1.getId()); + assertThat(user1).isEqualTo(user2); + user2.setId(2L); + assertThat(user1).isNotEqualTo(user2); + user1.setId(null); + assertThat(user1).isNotEqualTo(user2); + } + + @Test + void testUserDTOtoUser() { + AdminUserDTO userDTO = new AdminUserDTO(); + userDTO.setId(DEFAULT_ID); + userDTO.setLogin(DEFAULT_LOGIN); + userDTO.setFirstName(DEFAULT_FIRSTNAME); + userDTO.setLastName(DEFAULT_LASTNAME); + userDTO.setEmail(DEFAULT_EMAIL); + userDTO.setActivated(true); + userDTO.setImageUrl(DEFAULT_IMAGEURL); + userDTO.setLangKey(DEFAULT_LANGKEY); + userDTO.setCreatedBy(DEFAULT_LOGIN); + userDTO.setLastModifiedBy(DEFAULT_LOGIN); + userDTO.setAuthorities(Collections.singleton(AuthoritiesConstants.USER)); + + User user = userMapper.userDTOToUser(userDTO); + assertThat(user.getId()).isEqualTo(DEFAULT_ID); + assertThat(user.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(user.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(user.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(user.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(user.isActivated()).isTrue(); + assertThat(user.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(user.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + assertThat(user.getCreatedBy()).isNull(); + assertThat(user.getCreatedDate()).isNotNull(); + assertThat(user.getLastModifiedBy()).isNull(); + assertThat(user.getLastModifiedDate()).isNotNull(); + assertThat(user.getAuthorities()).extracting("name").containsExactly(AuthoritiesConstants.USER); + } + + @Test + void testUserToUserDTO() { + user.setId(DEFAULT_ID); + user.setCreatedBy(DEFAULT_LOGIN); + user.setCreatedDate(Instant.now()); + user.setLastModifiedBy(DEFAULT_LOGIN); + user.setLastModifiedDate(Instant.now()); + Set authorities = new HashSet<>(); + Authority authority = new Authority(); + authority.setName(AuthoritiesConstants.USER); + authorities.add(authority); + user.setAuthorities(authorities); + + AdminUserDTO userDTO = userMapper.userToAdminUserDTO(user); + + assertThat(userDTO.getId()).isEqualTo(DEFAULT_ID); + assertThat(userDTO.getLogin()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getFirstName()).isEqualTo(DEFAULT_FIRSTNAME); + assertThat(userDTO.getLastName()).isEqualTo(DEFAULT_LASTNAME); + assertThat(userDTO.getEmail()).isEqualTo(DEFAULT_EMAIL); + assertThat(userDTO.isActivated()).isTrue(); + assertThat(userDTO.getImageUrl()).isEqualTo(DEFAULT_IMAGEURL); + assertThat(userDTO.getLangKey()).isEqualTo(DEFAULT_LANGKEY); + assertThat(userDTO.getCreatedBy()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getCreatedDate()).isEqualTo(user.getCreatedDate()); + assertThat(userDTO.getLastModifiedBy()).isEqualTo(DEFAULT_LOGIN); + assertThat(userDTO.getLastModifiedDate()).isEqualTo(user.getLastModifiedDate()); + assertThat(userDTO.getAuthorities()).containsExactly(AuthoritiesConstants.USER); + assertThat(userDTO.toString()).isNotNull(); + } + + @Test + void testAuthorityEquals() { + Authority authorityA = new Authority(); + assertThat(authorityA).isNotEqualTo(null).isNotEqualTo(new Object()); + assertThat(authorityA.hashCode()).isZero(); + assertThat(authorityA.toString()).isNotNull(); + + Authority authorityB = new Authority(); + assertThat(authorityA).isEqualTo(authorityB); + + authorityB.setName(AuthoritiesConstants.ADMIN); + assertThat(authorityA).isNotEqualTo(authorityB); + + authorityA.setName(AuthoritiesConstants.USER); + assertThat(authorityA).isNotEqualTo(authorityB); + + authorityB.setName(AuthoritiesConstants.USER); + assertThat(authorityA).isEqualTo(authorityB).hasSameHashCodeAs(authorityB); + } + + private void assertPersistedUsers(Consumer> userAssertion) { + userAssertion.accept(userRepository.findAll()); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/WithUnauthenticatedMockUser.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/WithUnauthenticatedMockUser.java new file mode 100644 index 0000000..073b1b2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/WithUnauthenticatedMockUser.java @@ -0,0 +1,23 @@ +package de.novatec.ittage.app.web.rest; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@WithSecurityContext(factory = WithUnauthenticatedMockUser.Factory.class) +public @interface WithUnauthenticatedMockUser { + class Factory implements WithSecurityContextFactory { + + @Override + public SecurityContext createSecurityContext(WithUnauthenticatedMockUser annotation) { + return SecurityContextHolder.createEmptyContext(); + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorIT.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorIT.java new file mode 100644 index 0000000..8b5c51b --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorIT.java @@ -0,0 +1,117 @@ +package de.novatec.ittage.app.web.rest.errors; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import de.novatec.ittage.app.IntegrationTest; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +/** + * Integration tests {@link ExceptionTranslator} controller advice. + */ +@WithMockUser +@AutoConfigureMockMvc +@IntegrationTest +class ExceptionTranslatorIT { + + @Autowired + private MockMvc mockMvc; + + @Test + void testConcurrencyFailure() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/concurrency-failure")) + .andExpect(status().isConflict()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_CONCURRENCY_FAILURE)); + } + + @Test + void testMethodArgumentNotValid() throws Exception { + mockMvc + .perform(post("/api/exception-translator-test/method-argument").content("{}").contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value(ErrorConstants.ERR_VALIDATION)) + .andExpect(jsonPath("$.fieldErrors.[0].objectName").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].field").value("test")) + .andExpect(jsonPath("$.fieldErrors.[0].message").value("must not be null")); + } + + @Test + void testMissingServletRequestPartException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-part")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + void testMissingServletRequestParameterException() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/missing-servlet-request-parameter")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")); + } + + @Test + void testAccessDenied() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/access-denied")) + .andExpect(status().isForbidden()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.403")) + .andExpect(jsonPath("$.detail").value("test access denied!")); + } + + @Test + void testUnauthorized() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/unauthorized")) + .andExpect(status().isUnauthorized()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.401")) + .andExpect(jsonPath("$.path").value("/api/exception-translator-test/unauthorized")) + .andExpect(jsonPath("$.detail").value("test authentication failed!")); + } + + @Test + void testMethodNotSupported() throws Exception { + mockMvc + .perform(post("/api/exception-translator-test/access-denied")) + .andExpect(status().isMethodNotAllowed()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.405")) + .andExpect(jsonPath("$.detail").value("Request method 'POST' is not supported")); + } + + @Test + void testExceptionWithResponseStatus() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/response-status")) + .andExpect(status().isBadRequest()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.400")) + .andExpect(jsonPath("$.title").value("test response status")); + } + + @Test + void testInternalServerError() throws Exception { + mockMvc + .perform(get("/api/exception-translator-test/internal-server-error")) + .andExpect(status().isInternalServerError()) + .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON)) + .andExpect(jsonPath("$.message").value("error.http.500")) + .andExpect(jsonPath("$.title").value("Internal Server Error")); + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorTestController.java b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorTestController.java new file mode 100644 index 0000000..4f10bd1 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/java/de/novatec/ittage/app/web/rest/errors/ExceptionTranslatorTestController.java @@ -0,0 +1,66 @@ +package de.novatec.ittage.app.web.rest.errors; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; +import org.springframework.dao.ConcurrencyFailureException; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/exception-translator-test") +public class ExceptionTranslatorTestController { + + @GetMapping("/concurrency-failure") + public void concurrencyFailure() { + throw new ConcurrencyFailureException("test concurrency failure"); + } + + @PostMapping("/method-argument") + public void methodArgument(@Valid @RequestBody TestDTO testDTO) {} + + @GetMapping("/missing-servlet-request-part") + public void missingServletRequestPartException(@RequestPart String part) {} + + @GetMapping("/missing-servlet-request-parameter") + public void missingServletRequestParameterException(@RequestParam String param) {} + + @GetMapping("/access-denied") + public void accessdenied() { + throw new AccessDeniedException("test access denied!"); + } + + @GetMapping("/unauthorized") + public void unauthorized() { + throw new BadCredentialsException("test authentication failed!"); + } + + @GetMapping("/response-status") + public void exceptionWithResponseStatus() { + throw new TestResponseStatusException(); + } + + @GetMapping("/internal-server-error") + public void internalServerError() { + throw new RuntimeException(); + } + + public static class TestDTO { + + @NotNull + private String test; + + public String getTest() { + return test; + } + + public void setTest(String test) { + this.test = test; + } + } + + @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "test response status") + @SuppressWarnings("serial") + public static class TestResponseStatusException extends RuntimeException {} +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/.eslintrc.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/.eslintrc.json new file mode 100644 index 0000000..6e8814c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["cypress", "@typescript-eslint"], + "env": { + "cypress/globals": true + }, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:cypress/recommended"], + "rules": { + "@typescript-eslint/no-namespace": ["error", { "allowDeclarations": true }], + "@typescript-eslint/no-unused-vars": "off" + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/login-page.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/login-page.cy.ts new file mode 100644 index 0000000..0d2a025 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/login-page.cy.ts @@ -0,0 +1,61 @@ +import { + titleLoginSelector, + errorLoginSelector, + usernameLoginSelector, + passwordLoginSelector, + submitLoginSelector, +} from '../../support/commands'; + +describe('login modal', () => { + const username = Cypress.env('E2E_USERNAME') ?? 'user'; + const password = Cypress.env('E2E_PASSWORD') ?? 'user'; + + beforeEach(() => { + cy.visit(''); + cy.clickOnLoginItem(); + }); + + beforeEach(() => { + cy.intercept('POST', '/api/authenticate').as('authenticate'); + }); + + it('greets with signin', () => { + cy.get(titleLoginSelector).should('be.visible'); + }); + + it('greets visiting /login directly', () => { + cy.visit('/login'); + cy.get(titleLoginSelector).should('be.visible'); + }); + + it('requires username', () => { + cy.get(passwordLoginSelector).type('a-password'); + cy.get(submitLoginSelector).click(); + cy.wait('@authenticate').then(({ response }) => expect(response.statusCode).to.equal(400)); + // login page should stay open when login fails + cy.get(titleLoginSelector).should('be.visible'); + }); + + it('requires password', () => { + cy.get(usernameLoginSelector).type('a-login'); + cy.get(submitLoginSelector).click(); + cy.wait('@authenticate').then(({ response }) => expect(response.statusCode).to.equal(400)); + cy.get(errorLoginSelector).should('be.visible'); + }); + + it('errors when password is incorrect', () => { + cy.get(usernameLoginSelector).type(username); + cy.get(passwordLoginSelector).type('bad-password'); + cy.get(submitLoginSelector).click(); + cy.wait('@authenticate').then(({ response }) => expect(response.statusCode).to.equal(401)); + cy.get(errorLoginSelector).should('be.visible'); + }); + + it('go to login page when successfully logs in', () => { + cy.get(usernameLoginSelector).type(username); + cy.get(passwordLoginSelector).type(password); + cy.get(submitLoginSelector).click(); + cy.wait('@authenticate').then(({ response }) => expect(response.statusCode).to.equal(200)); + cy.hash().should('eq', ''); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/password-page.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/password-page.cy.ts new file mode 100644 index 0000000..f84eee6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/password-page.cy.ts @@ -0,0 +1,66 @@ +import { + currentPasswordSelector, + newPasswordSelector, + confirmPasswordSelector, + submitPasswordSelector, + classInvalid, + classValid, +} from '../../support/commands'; + +describe('/account/password', () => { + const username = Cypress.env('E2E_USERNAME') ?? 'user'; + const password = Cypress.env('E2E_PASSWORD') ?? 'user'; + + beforeEach(() => { + cy.login(username, password); + cy.visit('/account/password'); + }); + + beforeEach(() => { + cy.intercept('POST', '/api/account/change-password').as('passwordSave'); + }); + + it('should be accessible through menu', () => { + cy.visit(''); + cy.clickOnPasswordItem(); + cy.url().should('match', /\/account\/password$/); + }); + + it('requires current password', () => { + cy.get(currentPasswordSelector).should('have.class', classInvalid); + cy.get(currentPasswordSelector).type('wrong-current-password'); + cy.get(currentPasswordSelector).blur(); + cy.get(currentPasswordSelector).should('have.class', classValid); + }); + + it('requires new password', () => { + cy.get(newPasswordSelector).should('have.class', classInvalid); + cy.get(newPasswordSelector).type('jhipster'); + cy.get(newPasswordSelector).blur(); + cy.get(newPasswordSelector).should('have.class', classValid); + }); + + it('requires confirm new password', () => { + cy.get(newPasswordSelector).type('jhipster'); + cy.get(confirmPasswordSelector).should('have.class', classInvalid); + cy.get(confirmPasswordSelector).type('jhipster'); + cy.get(confirmPasswordSelector).blur(); + cy.get(confirmPasswordSelector).should('have.class', classValid); + }); + + it('should fail to update password when using incorrect current password', () => { + cy.get(currentPasswordSelector).type('wrong-current-password'); + cy.get(newPasswordSelector).type('jhipster'); + cy.get(confirmPasswordSelector).type('jhipster'); + cy.get(submitPasswordSelector).click(); + cy.wait('@passwordSave').then(({ response }) => expect(response.statusCode).to.equal(400)); + }); + + it('should be able to update password', () => { + cy.get(currentPasswordSelector).type(password); + cy.get(newPasswordSelector).type(password); + cy.get(confirmPasswordSelector).type(password); + cy.get(submitPasswordSelector).click(); + cy.wait('@passwordSave').then(({ response }) => expect(response.statusCode).to.equal(200)); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/register-page.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/register-page.cy.ts new file mode 100644 index 0000000..c9732eb --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/register-page.cy.ts @@ -0,0 +1,89 @@ +import { + usernameRegisterSelector, + emailRegisterSelector, + firstPasswordRegisterSelector, + secondPasswordRegisterSelector, + submitRegisterSelector, + classInvalid, + classValid, +} from '../../support/commands'; + +describe('/account/register', () => { + beforeEach(() => { + cy.visit('/account/register'); + }); + + beforeEach(() => { + cy.intercept('POST', '/api/register').as('registerSave'); + }); + + it('should be accessible through menu', () => { + cy.visit(''); + cy.clickOnRegisterItem(); + cy.url().should('match', /\/account\/register$/); + }); + + it('should load the register page', () => { + cy.get(submitRegisterSelector).should('be.visible'); + }); + + it('requires username', () => { + cy.get(usernameRegisterSelector).should('have.class', classInvalid); + cy.get(usernameRegisterSelector).type('test'); + cy.get(usernameRegisterSelector).blur(); + cy.get(usernameRegisterSelector).should('have.class', classValid); + }); + + it('should not accept invalid email', () => { + cy.get(emailRegisterSelector).should('have.class', classInvalid); + cy.get(emailRegisterSelector).type('testtest.fr'); + cy.get(emailRegisterSelector).blur(); + cy.get(emailRegisterSelector).should('have.class', classInvalid); + }); + + it('requires email in correct format', () => { + cy.get(emailRegisterSelector).should('have.class', classInvalid); + cy.get(emailRegisterSelector).type('test@test.fr'); + cy.get(emailRegisterSelector).blur(); + cy.get(emailRegisterSelector).should('have.class', classValid); + }); + + it('requires first password', () => { + cy.get(firstPasswordRegisterSelector).should('have.class', classInvalid); + cy.get(firstPasswordRegisterSelector).type('test@test.fr'); + cy.get(firstPasswordRegisterSelector).blur(); + cy.get(firstPasswordRegisterSelector).should('have.class', classValid); + }); + + it('requires password and confirm password to be same', () => { + cy.get(firstPasswordRegisterSelector).should('have.class', classInvalid); + cy.get(firstPasswordRegisterSelector).type('test'); + cy.get(firstPasswordRegisterSelector).blur(); + cy.get(firstPasswordRegisterSelector).should('have.class', classValid); + cy.get(secondPasswordRegisterSelector).should('have.class', classInvalid); + cy.get(secondPasswordRegisterSelector).type('test'); + cy.get(secondPasswordRegisterSelector).blur(); + cy.get(secondPasswordRegisterSelector).should('have.class', classValid); + }); + + it('requires password and confirm password have not the same value', () => { + cy.get(firstPasswordRegisterSelector).should('have.class', classInvalid); + cy.get(firstPasswordRegisterSelector).type('test'); + cy.get(firstPasswordRegisterSelector).blur(); + cy.get(firstPasswordRegisterSelector).should('have.class', classValid); + cy.get(secondPasswordRegisterSelector).should('have.class', classInvalid); + cy.get(secondPasswordRegisterSelector).type('otherPassword'); + cy.get(submitRegisterSelector).should('be.disabled'); + }); + + it('register a valid user', () => { + const randomEmail = 'Edwina77@yahoo.com'; + const randomUsername = 'Bridie70'; + cy.get(usernameRegisterSelector).type(randomUsername); + cy.get(emailRegisterSelector).type(randomEmail); + cy.get(firstPasswordRegisterSelector).type('jondoe'); + cy.get(secondPasswordRegisterSelector).type('jondoe'); + cy.get(submitRegisterSelector).click(); + cy.wait('@registerSave').then(({ response }) => expect(response.statusCode).to.equal(201)); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/reset-password-page.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/reset-password-page.cy.ts new file mode 100644 index 0000000..dbe3ef2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/reset-password-page.cy.ts @@ -0,0 +1,35 @@ +import { + usernameLoginSelector, + forgetYourPasswordSelector, + emailResetPasswordSelector, + submitInitResetPasswordSelector, + classInvalid, + classValid, +} from '../../support/commands'; + +describe('forgot your password', () => { + const username = Cypress.env('E2E_USERNAME') ?? 'user'; + + beforeEach(() => { + cy.visit(''); + cy.clickOnLoginItem(); + cy.get(usernameLoginSelector).type(username); + cy.get(forgetYourPasswordSelector).click(); + }); + + beforeEach(() => { + cy.intercept('POST', '/api/account/reset-password/init').as('initResetPassword'); + }); + + it('requires email', () => { + cy.get(emailResetPasswordSelector).should('have.class', classInvalid); + cy.get(emailResetPasswordSelector).type('user@gmail.com'); + cy.get(emailResetPasswordSelector).should('have.class', classValid); + }); + + it('should be able to init reset password', () => { + cy.get(emailResetPasswordSelector).type('user@gmail.com'); + cy.get(submitInitResetPasswordSelector).click({ force: true }); + cy.wait('@initResetPassword').then(({ response }) => expect(response.statusCode).to.equal(200)); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/settings-page.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/settings-page.cy.ts new file mode 100644 index 0000000..a5caea3 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/account/settings-page.cy.ts @@ -0,0 +1,102 @@ +import { firstNameSettingsSelector, lastNameSettingsSelector, submitSettingsSelector, emailSettingsSelector } from '../../support/commands'; +import type { Account } from '../../support/account'; + +describe('/account/settings', () => { + const adminUsername = Cypress.env('E2E_USERNAME') ?? 'admin'; + const adminPassword = Cypress.env('E2E_PASSWORD') ?? 'admin'; + const username = Cypress.env('E2E_USERNAME') ?? 'user'; + const password = Cypress.env('E2E_PASSWORD') ?? 'user'; + + const testUserEmail = 'user@localhost.fr'; + let originalUserAccount: Account; + let testUserAccount: Account; + + before(() => { + cy.login(username, password); + + cy.getAccount().then(account => { + originalUserAccount = account; + testUserAccount = { ...account, email: testUserEmail }; + + // need to modify email because default email does not match regex in some frameworks + cy.saveAccount(testUserAccount).its('status').should('eq', 200); + }); + }); + + beforeEach(() => { + cy.login(username, password); + cy.visit('/account/settings'); + cy.get(emailSettingsSelector).should('have.value', testUserEmail); + + cy.intercept('POST', '/api/account').as('settingsSave'); + }); + + afterEach(() => { + cy.login(username, password); + cy.saveAccount(testUserAccount).its('status').should('eq', 200); + }); + + after(() => { + cy.login(username, password); + cy.saveAccount(originalUserAccount).its('status').should('eq', 200); + }); + + it('should be accessible through menu', () => { + cy.visit(''); + cy.clickOnSettingsItem(); + cy.url().should('match', /\/account\/settings$/); + }); + + it("should be able to change 'user' firstname settings", () => { + cy.get(firstNameSettingsSelector).clear(); + cy.get(firstNameSettingsSelector).type('jhipster'); + cy.get(submitSettingsSelector).click(); + cy.wait('@settingsSave').then(({ response }) => expect(response?.statusCode).to.equal(200)); + }); + + it("should be able to change 'user' lastname settings", () => { + cy.get(lastNameSettingsSelector).clear(); + cy.get(firstNameSettingsSelector).type('jhipster'); + cy.get(lastNameSettingsSelector).type('retspihj'); + cy.get(submitSettingsSelector).click(); + cy.wait('@settingsSave').then(({ response }) => expect(response?.statusCode).to.equal(200)); + }); + + it("should be able to change 'user' email settings", () => { + cy.get(emailSettingsSelector).clear(); + cy.get(firstNameSettingsSelector).type('jhipster'); + cy.get(emailSettingsSelector).type('user@localhost.fr'); + cy.get(submitSettingsSelector).click(); + cy.wait('@settingsSave').then(({ response }) => expect(response?.statusCode).to.equal(200)); + }); + + describe('if there is another user with an email', () => { + let originalAdminAccount: Account; + const testAdminEmail = 'admin@localhost.fr'; + + before(() => { + cy.login(adminUsername, adminPassword); + cy.getAccount().then(account => { + originalAdminAccount = account; + + // need to modify email because default email does not match regex in some frameworks + cy.saveAccount({ ...account, email: testAdminEmail }) + .its('status') + .should('eq', 200); + }); + }); + + after(() => { + cy.login(adminUsername, adminPassword); + cy.saveAccount(originalAdminAccount).its('status').should('eq', 200); + }); + + it("should not be able to change 'user' email to same value", () => { + cy.get(emailSettingsSelector).clear(); + cy.get(firstNameSettingsSelector).type('jhipster'); + cy.get(emailSettingsSelector).type(testAdminEmail); + cy.get(submitSettingsSelector).click(); + cy.wait('@settingsSave').then(({ response }) => expect(response?.statusCode).to.equal(400)); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/administration/administration.cy.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/administration/administration.cy.ts new file mode 100644 index 0000000..c0dbd32 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/e2e/administration/administration.cy.ts @@ -0,0 +1,76 @@ +import { + userManagementPageHeadingSelector, + metricsPageHeadingSelector, + healthPageHeadingSelector, + logsPageHeadingSelector, + configurationPageHeadingSelector, + swaggerPageSelector, + swaggerFrameSelector, +} from '../../support/commands'; + +describe('/admin', () => { + const username = Cypress.env('E2E_USERNAME') ?? 'admin'; + const password = Cypress.env('E2E_PASSWORD') ?? 'admin'; + + beforeEach(() => { + cy.login(username, password); + cy.visit(''); + }); + + describe('/user-management', () => { + it('should load the page', () => { + cy.clickOnAdminMenuItem('user-management'); + cy.get(userManagementPageHeadingSelector).should('be.visible'); + }); + }); + + describe('/metrics', () => { + it('should load the page', () => { + cy.clickOnAdminMenuItem('metrics'); + cy.get(metricsPageHeadingSelector).should('be.visible'); + }); + }); + + describe('/health', () => { + it('should load the page', () => { + cy.clickOnAdminMenuItem('health'); + cy.get(healthPageHeadingSelector).should('be.visible'); + }); + }); + + describe('/logs', () => { + it('should load the page', () => { + cy.clickOnAdminMenuItem('logs'); + cy.get(logsPageHeadingSelector).should('be.visible'); + }); + }); + + describe('/configuration', () => { + it('should load the page', () => { + cy.clickOnAdminMenuItem('configuration'); + cy.get(configurationPageHeadingSelector).should('be.visible'); + }); + }); + + describe('/docs', () => { + it('should load the page', () => { + cy.getManagementInfo().then(info => { + if (info.activeProfiles.includes('api-docs')) { + cy.clickOnAdminMenuItem('docs'); + cy.get(swaggerFrameSelector) + .should('be.visible') + .then(() => { + // Wait iframe to load + cy.wait(500); // eslint-disable-line cypress/no-unnecessary-waiting + const getSwaggerIframe = () => { + return cy.get(swaggerFrameSelector).its('0.contentDocument.body').should('not.be.empty').then(cy.wrap); + }; + getSwaggerIframe().find(swaggerPageSelector, { timeout: 15000 }).should('be.visible'); + getSwaggerIframe().find('[id="select"] > option').its('length').should('be.gt', 0); + getSwaggerIframe().find(swaggerPageSelector).then(cy.wrap).find('.information-container').its('length').should('be.gt', 0); + }); + } + }); + }); + }); +}); diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/fixtures/integration-test.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/fixtures/integration-test.png new file mode 100644 index 0000000..5d31c2f Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/fixtures/integration-test.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/plugins/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/plugins/index.ts new file mode 100644 index 0000000..034e6be --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/plugins/index.ts @@ -0,0 +1,35 @@ +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +export default async (on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) => { + on('before:browser:launch', (browser, launchOptions) => { + if (browser.name === 'chrome' && browser.isHeadless) { + launchOptions.args.push('--disable-gpu'); + return launchOptions; + } + }); + + // Allows logging with cy.task('log', 'message') or cy.task('table', object) + on('task', { + log(message) { + console.log(message); + return null; + }, + table(message) { + console.table(message); + return null; + }, + }); + + return config; +}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/account.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/account.ts new file mode 100644 index 0000000..da5d368 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/account.ts @@ -0,0 +1,27 @@ +export type Account = Record; + +Cypress.Commands.add('getAccount', () => { + return cy + .authenticatedRequest({ + method: 'GET', + url: '/api/account', + }) + .then(response => response.body as Account); +}); + +Cypress.Commands.add('saveAccount', (account: Account) => { + return cy.authenticatedRequest({ + method: 'POST', + url: '/api/account', + body: account, + }); +}); + +declare global { + namespace Cypress { + interface Chainable { + getAccount(): Cypress.Chainable; + saveAccount(account: Account): Cypress.Chainable>; + } + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/commands.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/commands.ts new file mode 100644 index 0000000..8ef9106 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/commands.ts @@ -0,0 +1,130 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-use-before-define */ +// eslint-disable-next-line spaced-comment +/// + +// *********************************************** +// This commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +// *********************************************** +// Begin Specific Selector Attributes for Cypress +// *********************************************** + +// Navbar +export const navbarSelector = '[data-cy="navbar"]'; +export const adminMenuSelector = '[data-cy="adminMenu"]'; +export const accountMenuSelector = '[data-cy="accountMenu"]'; +export const registerItemSelector = '[data-cy="register"]'; +export const settingsItemSelector = '[data-cy="settings"]'; +export const passwordItemSelector = '[data-cy="passwordItem"]'; +export const loginItemSelector = '[data-cy="login"]'; +export const logoutItemSelector = '[data-cy="logout"]'; +export const entityItemSelector = '[data-cy="entity"]'; + +// Login +export const titleLoginSelector = '[data-cy="loginTitle"]'; +export const errorLoginSelector = '[data-cy="loginError"]'; +export const usernameLoginSelector = '[data-cy="username"]'; +export const passwordLoginSelector = '[data-cy="password"]'; +export const forgetYourPasswordSelector = '[data-cy="forgetYourPasswordSelector"]'; +export const submitLoginSelector = '[data-cy="submit"]'; + +// Register +export const titleRegisterSelector = '[data-cy="registerTitle"]'; +export const usernameRegisterSelector = '[data-cy="username"]'; +export const emailRegisterSelector = '[data-cy="email"]'; +export const firstPasswordRegisterSelector = '[data-cy="firstPassword"]'; +export const secondPasswordRegisterSelector = '[data-cy="secondPassword"]'; +export const submitRegisterSelector = '[data-cy="submit"]'; + +// Settings +export const firstNameSettingsSelector = '[data-cy="firstname"]'; +export const lastNameSettingsSelector = '[data-cy="lastname"]'; +export const emailSettingsSelector = '[data-cy="email"]'; +export const languageSettingsSelector = '[data-cy="langKey"]'; +export const submitSettingsSelector = '[data-cy="submit"]'; + +// Password +export const currentPasswordSelector = '[data-cy="currentPassword"]'; +export const newPasswordSelector = '[data-cy="newPassword"]'; +export const confirmPasswordSelector = '[data-cy="confirmPassword"]'; +export const submitPasswordSelector = '[data-cy="submit"]'; + +// Reset Password +export const emailResetPasswordSelector = '[data-cy="emailResetPassword"]'; +export const submitInitResetPasswordSelector = '[data-cy="submit"]'; + +// Administration +export const userManagementPageHeadingSelector = '[data-cy="userManagementPageHeading"]'; +export const swaggerFrameSelector = 'iframe[data-cy="swagger-frame"]'; +export const swaggerPageSelector = '[id="swagger-ui"]'; +export const metricsPageHeadingSelector = '[data-cy="metricsPageHeading"]'; +export const healthPageHeadingSelector = '[data-cy="healthPageHeading"]'; +export const logsPageHeadingSelector = '[data-cy="logsPageHeading"]'; +export const configurationPageHeadingSelector = '[data-cy="configurationPageHeading"]'; + +// *********************************************** +// End Specific Selector Attributes for Cypress +// *********************************************** + +export const classInvalid = 'ng-invalid'; + +export const classValid = 'ng-valid'; + +Cypress.Commands.add('authenticatedRequest', data => { + const jwtToken = sessionStorage.getItem(Cypress.env('jwtStorageName')); + const bearerToken = jwtToken && JSON.parse(jwtToken); + if (bearerToken) { + return cy.request({ + ...data, + auth: { + bearer: bearerToken, + }, + }); + } + return cy.request(data); +}); + +Cypress.Commands.add('login', (username: string, password: string) => { + cy.session( + [username, password], + () => { + cy.request({ + method: 'GET', + url: '/api/account', + failOnStatusCode: false, + }); + cy.authenticatedRequest({ + method: 'POST', + body: { username, password }, + url: Cypress.env('authenticationUrl'), + }).then(({ body: { id_token } }) => { + sessionStorage.setItem(Cypress.env('jwtStorageName'), JSON.stringify(id_token)); + }); + }, + { + validate() { + cy.authenticatedRequest({ url: '/api/account' }).its('status').should('eq', 200); + }, + }, + ); +}); + +declare global { + namespace Cypress { + interface Chainable { + authenticatedRequest(data): Cypress.Chainable; + login(username: string, password: string): Cypress.Chainable; + } + } +} + +// Convert this to a module instead of script (allows import/export) +export {}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/entity.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/entity.ts new file mode 100644 index 0000000..9044584 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/entity.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-use-before-define */ +// eslint-disable-next-line spaced-comment +/// + +// *********************************************** +// Begin Specific Selector Attributes for Cypress +// *********************************************** + +// Entity +export const entityTableSelector = '[data-cy="entityTable"]'; +export const entityCreateButtonSelector = '[data-cy="entityCreateButton"]'; +export const entityCreateSaveButtonSelector = '[data-cy="entityCreateSaveButton"]'; +export const entityCreateCancelButtonSelector = '[data-cy="entityCreateCancelButton"]'; +export const entityDetailsButtonSelector = '[data-cy="entityDetailsButton"]'; // can return multiple elements +export const entityDetailsBackButtonSelector = '[data-cy="entityDetailsBackButton"]'; +export const entityEditButtonSelector = '[data-cy="entityEditButton"]'; +export const entityDeleteButtonSelector = '[data-cy="entityDeleteButton"]'; +export const entityConfirmDeleteButtonSelector = '[data-cy="entityConfirmDeleteButton"]'; + +// *********************************************** +// End Specific Selector Attributes for Cypress +// *********************************************** + +Cypress.Commands.add('getEntityHeading', (entityName: string) => { + return cy.get(`[data-cy="${entityName}Heading"]`); +}); + +Cypress.Commands.add('getEntityCreateUpdateHeading', (entityName: string) => { + return cy.get(`[data-cy="${entityName}CreateUpdateHeading"]`); +}); + +Cypress.Commands.add('getEntityDetailsHeading', (entityInstanceName: string) => { + return cy.get(`[data-cy="${entityInstanceName}DetailsHeading"]`); +}); + +Cypress.Commands.add('getEntityDeleteDialogHeading', (entityInstanceName: string) => { + return cy.get(`[data-cy="${entityInstanceName}DeleteDialogHeading"]`); +}); + +Cypress.Commands.add('setFieldImageAsBytesOfEntity', (fieldName: string, fileName: string, mimeType: string) => { + // fileName is the image which you have already put in cypress fixture folder + // should be like : 'integration-test.png', 'image/png' + cy.fixture(fileName) + .as('image') + .get(`[data-cy="${fieldName}"]`) + .then(function (el) { + const blob = Cypress.Blob.base64StringToBlob(this.image, mimeType); + const file = new File([blob], fileName, { type: mimeType }); + const list = new DataTransfer(); + list.items.add(file); + const myFileList = list.files; + (el[0] as HTMLInputElement).files = myFileList; + el[0].dispatchEvent(new Event('change', { bubbles: true })); + }); +}); + +Cypress.Commands.add('setFieldSelectToLastOfEntity', (fieldName: string) => { + return cy.get(`[data-cy="${fieldName}"]`).then(select => { + const selectSize = (select[0] as HTMLSelectElement)?.options?.length || Number(select.attr('size')) || 0; + if (selectSize > 0) { + return cy.get(`[data-cy="${fieldName}"] option`).then((options: JQuery) => { + const elements = [...options].map((o: HTMLElement) => (o as HTMLOptionElement).label); + const lastElement = elements.length - 1; + cy.get(`[data-cy="${fieldName}"]`).select(lastElement); + cy.get(`[data-cy="${fieldName}"]`).type('{downarrow}'); + }); + } else { + return cy.get(`[data-cy="${fieldName}"]`).type('{downarrow}'); + } + }); +}); + +declare global { + namespace Cypress { + interface Chainable { + getEntityHeading(entityName: string): Cypress.Chainable; + getEntityCreateUpdateHeading(entityName: string): Cypress.Chainable; + getEntityDetailsHeading(entityInstanceName: string): Cypress.Chainable; + getEntityDeleteDialogHeading(entityInstanceName: string): Cypress.Chainable; + setFieldImageAsBytesOfEntity(fieldName: string, fileName: string, mimeType: string): Cypress.Chainable; + setFieldSelectToLastOfEntity(fieldName: string): Cypress.Chainable; + } + } +} + +// Convert this to a module instead of script (allows import/export) +export {}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/index.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/index.ts new file mode 100644 index 0000000..96bd01c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/index.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +import './account'; +import './commands'; +import './navbar'; +import './entity'; +import './management'; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/management.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/management.ts new file mode 100644 index 0000000..b8d8701 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/management.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-use-before-define */ + +Cypress.Commands.add('getManagementInfo', () => { + return cy + .request({ + method: 'GET', + url: '/management/info', + }) + .then(response => response.body); +}); + +declare global { + namespace Cypress { + interface Chainable { + getManagementInfo(): Cypress.Chainable; + } + } +} + +// Convert this to a module instead of script (allows import/export) +export {}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/navbar.ts b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/navbar.ts new file mode 100644 index 0000000..8a06068 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/support/navbar.ts @@ -0,0 +1,66 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-use-before-define */ + +import { + navbarSelector, + adminMenuSelector, + accountMenuSelector, + registerItemSelector, + loginItemSelector, + logoutItemSelector, + settingsItemSelector, + passwordItemSelector, + entityItemSelector, +} from './commands'; + +Cypress.Commands.add('clickOnLoginItem', () => { + cy.get(navbarSelector).get(accountMenuSelector).click(); + return cy.get(navbarSelector).get(accountMenuSelector).get(loginItemSelector).click(); +}); + +Cypress.Commands.add('clickOnLogoutItem', () => { + cy.get(navbarSelector).get(accountMenuSelector).click(); + return cy.get(navbarSelector).get(accountMenuSelector).get(logoutItemSelector).click(); +}); + +Cypress.Commands.add('clickOnRegisterItem', () => { + cy.get(navbarSelector).get(accountMenuSelector).click(); + return cy.get(navbarSelector).get(accountMenuSelector).get(registerItemSelector).click(); +}); + +Cypress.Commands.add('clickOnSettingsItem', () => { + cy.get(navbarSelector).get(accountMenuSelector).click(); + return cy.get(navbarSelector).get(accountMenuSelector).get(settingsItemSelector).click(); +}); + +Cypress.Commands.add('clickOnPasswordItem', () => { + cy.get(navbarSelector).get(accountMenuSelector).click(); + return cy.get(navbarSelector).get(accountMenuSelector).get(passwordItemSelector).click(); +}); + +Cypress.Commands.add('clickOnAdminMenuItem', (item: string) => { + cy.get(navbarSelector).get(adminMenuSelector).click(); + return cy.get(navbarSelector).get(adminMenuSelector).get(`.dropdown-item[href="/admin/${item}"]`).click(); +}); + +Cypress.Commands.add('clickOnEntityMenuItem', (entityName: string) => { + cy.get(navbarSelector).get(entityItemSelector).click(); + return cy.get(navbarSelector).get(entityItemSelector).get(`.dropdown-item[href="/${entityName}"]`).click({ force: true }); +}); + +declare global { + namespace Cypress { + interface Chainable { + clickOnLoginItem(): Cypress.Chainable; + clickOnLogoutItem(): Cypress.Chainable; + clickOnRegisterItem(): Cypress.Chainable; + clickOnSettingsItem(): Cypress.Chainable; + clickOnPasswordItem(): Cypress.Chainable; + clickOnAdminMenuItem(item: string): Cypress.Chainable; + clickOnEntityMenuItem(entityName: string): Cypress.Chainable; + } + } +} + +// Convert this to a module instead of script (allows import/export) +export {}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/tsconfig.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/tsconfig.json new file mode 100644 index 0000000..957d5be --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/javascript/cypress/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "baseUrl": "./", + "sourceMap": false, + "outDir": "../../../../target/cypress/tsc", + "target": "es2018", + "types": ["cypress", "node"] + }, + "include": ["./../../../../cypress*.config.ts", "./**/*.ts"] +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/META-INF/spring.factories b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/META-INF/spring.factories new file mode 100644 index 0000000..469ef9c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.test.context.ContextCustomizerFactory = de.novatec.ittage.\ + app.config.SqlTestContainersSpringContextCustomizerFactory \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testdev.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testdev.yml new file mode 100644 index 0000000..a61e286 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testdev.yml @@ -0,0 +1,40 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests with testcontainers database containers. +# +# To activate this configuration launch integration tests with the 'testcontainers' profile +# +# More information on database containers: https://www.testcontainers.org/modules/databases/ +# =================================================================== + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + hikari: + auto-commit: false + poolName: Hikari + maximum-pool-size: 1 + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + jpa: + open-in-view: false + hibernate: + ddl-auto: none + naming: + physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + properties: + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + hibernate.hbm2ddl.auto: none #TODO: temp relief for integration tests, revisit required + hibernate.type.preferred_instant_jdbc_type: TIMESTAMP + hibernate.jdbc.time_zone: UTC + hibernate.timezone.default_storage: NORMALIZE + hibernate.query.fail_on_pagination_over_collection_fetch: true diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testprod.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testprod.yml new file mode 100644 index 0000000..6098037 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application-testprod.yml @@ -0,0 +1,40 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests with testcontainers database containers. +# +# To activate this configuration launch integration tests with the 'testcontainers' profile +# +# More information on database containers: https://www.testcontainers.org/modules/databases/ +# =================================================================== + +spring: + datasource: + type: com.zaxxer.hikari.HikariDataSource + hikari: + poolName: Hikari + auto-commit: false + maximum-pool-size: 1 + data-source-properties: + cachePrepStmts: true + prepStmtCacheSize: 250 + prepStmtCacheSqlLimit: 2048 + useServerPrepStmts: true + jpa: + open-in-view: false + hibernate: + ddl-auto: none + naming: + physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy + implicit-strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy + properties: + hibernate.id.new_generator_mappings: true + hibernate.connection.provider_disables_autocommit: true + hibernate.cache.use_second_level_cache: false + hibernate.cache.use_query_cache: false + hibernate.generate_statistics: false + hibernate.hbm2ddl.auto: none #TODO: temp relief for integration tests, revisit required + hibernate.type.preferred_instant_jdbc_type: TIMESTAMP + hibernate.jdbc.time_zone: UTC + hibernate.timezone.default_storage: NORMALIZE + hibernate.query.fail_on_pagination_over_collection_fetch: true diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application.yml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application.yml new file mode 100644 index 0000000..1c06f4d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/config/application.yml @@ -0,0 +1,90 @@ +# =================================================================== +# Spring Boot configuration. +# +# This configuration is used for unit/integration tests. +# +# More information on profiles: https://www.jhipster.tech/profiles/ +# More information on configuration properties: https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# =================================================================== +# Standard Spring Boot properties. +# Full reference is available at: +# http://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html +# =================================================================== + +spring: + application: + name: app + # Replace by 'prod, faker' to add the faker context and have sample data loaded in production + liquibase: + contexts: test + jackson: + serialization: + write-durations-as-timestamps: false + mail: + host: localhost + main: + allow-bean-definition-overriding: true + messages: + basename: i18n/messages + task: + execution: + thread-name-prefix: app-task- + pool: + core-size: 1 + max-size: 50 + queue-capacity: 10000 + scheduling: + thread-name-prefix: app-scheduling- + pool: + size: 20 + thymeleaf: + mode: HTML + +server: + port: 10344 + address: localhost + +# =================================================================== +# JHipster specific properties +# +# Full reference is available at: https://www.jhipster.tech/common-application-properties/ +# =================================================================== +jhipster: + clientApp: + name: 'appApp' + mail: + from: app@localhost.com + base-url: http://127.0.0.1:8080 + logging: + # To test json console appender + use-json-format: false + logstash: + enabled: false + host: localhost + port: 5000 + ring-buffer-size: 512 + security: + authentication: + jwt: + # This token must be encoded using Base64 (you can type `echo 'secret-key'|base64` on your command line) + base64-secret: MmQ3MzkwYzgzMzMxOTYwN2IxMGViYTM4MjlmNThkN2JhMzNkMDQ5ZjIyNzJkNDhlNWNkZGNhZDdjYzRiNzIyMjVlYTAwMmI5NTA3ZmIxZTQzODNhZWY5OWFjZTBiNTVmODlhMzY4NDhjMmQ1Y2UzNGMwMTEzNWFiNWU5YWEwMTY= + # Token is valid 24 hours + token-validity-in-seconds: 86400 + token-validity-in-seconds-for-remember-me: 86400 + +# =================================================================== +# Application specific properties +# Add your own application properties here, see the ApplicationProperties class +# to have type-safe configuration, like in the JHipsterProperties above +# +# More documentation is available at: +# https://www.jhipster.tech/common-application-properties/ +# =================================================================== + +# application: +management: + health: + mail: + enabled: false diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_de.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_de.properties new file mode 100644 index 0000000..f2c4b6c --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_de.properties @@ -0,0 +1,4 @@ +email.test.title=test title +# Value used for German locale unit test in MailServiceIT +# as this file is loaded instead of real file +email.activation.title=Aktivierung app diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_en.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_en.properties new file mode 100644 index 0000000..e7d2c4a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_en.properties @@ -0,0 +1,4 @@ +email.test.title=test title +# Value used for English locale unit test in MailServiceIT +# as this file is loaded instead of real file +email.activation.title=app account activation diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_fr.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_fr.properties new file mode 100644 index 0000000..392fcfc --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/i18n/messages_fr.properties @@ -0,0 +1,4 @@ +email.test.title=test title +# Value used for English locale unit test in MailServiceIT +# as this file is loaded instead of real file +email.activation.title=Activation de votre compte app \ No newline at end of file diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/junit-platform.properties b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..f79da59 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/junit-platform.properties @@ -0,0 +1,4 @@ +junit.jupiter.execution.timeout.default = 15 s +junit.jupiter.execution.timeout.testable.method.default = 15 s +junit.jupiter.execution.timeout.beforeall.method.default = 60 s +junit.jupiter.testclass.order.default=de.novatec.ittage.app.config.SpringBootTestClassOrderer diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/logback.xml b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/logback.xml new file mode 100644 index 0000000..7dcd70f --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/logback.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/activationEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/activationEmail.html new file mode 100644 index 0000000..6db87f6 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/activationEmail.html @@ -0,0 +1,19 @@ + + + + JHipster activation + + + +

Dear

+

Your JHipster account has been created, please click on the URL below to activate it:

+

+ Activation link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/creationEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/creationEmail.html new file mode 100644 index 0000000..07075e0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/creationEmail.html @@ -0,0 +1,19 @@ + + + + JHipster creation + + + +

Dear

+

Your JHipster account has been created, please click on the URL below to access it:

+

+ Login link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/passwordResetEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/passwordResetEmail.html new file mode 100644 index 0000000..6ddc5d2 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/passwordResetEmail.html @@ -0,0 +1,21 @@ + + + + JHipster password reset + + + +

Dear

+

+ For your JHipster account a password reset was requested, please click on the URL below to reset it: +

+

+ Login link +

+

+ Regards, +
+ JHipster. +

+ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/testEmail.html b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/testEmail.html new file mode 100644 index 0000000..a4ca16a --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/src/test/resources/templates/mail/testEmail.html @@ -0,0 +1 @@ + diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.app.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.app.json new file mode 100644 index 0000000..099fec0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./target/out-tsc/app", + "types": ["@angular/localize"] + }, + "files": ["src/main/webapp/main.ts"], + "include": ["src/main/webapp/**/*.d.ts"] +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.json new file mode 100644 index 0000000..e4478e0 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.json @@ -0,0 +1,35 @@ +{ + "compilerOptions": { + "baseUrl": "src/main/webapp/", + "outDir": "./target/out-tsc/root", + "forceConsistentCasingInFileNames": true, + "strict": true, + "strictNullChecks": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "useDefineForClassFields": false, + "target": "es2022", + "module": "es2020", + "types": [], + "lib": ["es2018", "es2020", "dom"] + }, + "references": [ + { + "path": "tsconfig.spec.json" + } + ], + "angularCompilerOptions": { + "strictInjectionParameters": true, + "strictInputAccessModifiers": true, + "strictTemplates": true, + "preserveWhitespaces": true + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.spec.json b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.spec.json new file mode 100644 index 0000000..d00c687 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/main/webapp/**/*.ts"], + "compilerOptions": { + "composite": true, + "outDir": "target/out-tsc/spec", + "types": ["jest", "node"] + } +} diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/environment.js b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/environment.js new file mode 100644 index 0000000..d021177 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/environment.js @@ -0,0 +1,6 @@ +module.exports = { + I18N_HASH: 'generated_hash', + SERVER_API_URL: '', + __VERSION__: process.env.hasOwnProperty('APP_VERSION') ? process.env.APP_VERSION : 'DEV', + __DEBUG_INFO_ENABLED__: false, +}; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/logo-jhipster.png b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/logo-jhipster.png new file mode 100644 index 0000000..e301aa9 Binary files /dev/null and b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/logo-jhipster.png differ diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/proxy.conf.js b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/proxy.conf.js new file mode 100644 index 0000000..642725d --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/proxy.conf.js @@ -0,0 +1,14 @@ +function setupProxy({ tls }) { + const serverResources = ['/api', '/services', '/management', '/v3/api-docs', '/h2-console', '/auth', '/health']; + const conf = [ + { + context: serverResources, + target: `http${tls ? 's' : ''}://localhost:8080`, + secure: false, + changeOrigin: tls, + }, + ]; + return conf; +} + +module.exports = setupProxy; diff --git a/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/webpack.custom.js b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/webpack.custom.js new file mode 100644 index 0000000..6a70892 --- /dev/null +++ b/2_supplychain/2_praxis/DemoContainerSecurity/Demo/app/webpack/webpack.custom.js @@ -0,0 +1,141 @@ +const webpack = require('webpack'); +const { merge } = require('webpack-merge'); +const path = require('path'); +const { hashElement } = require('folder-hash'); +const MergeJsonWebpackPlugin = require('merge-jsons-webpack-plugin'); +const BrowserSyncPlugin = require('browser-sync-webpack-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; +const WebpackNotifierPlugin = require('webpack-notifier'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const ESLintPlugin = require('eslint-webpack-plugin'); + +const environment = require('./environment'); +const proxyConfig = require('./proxy.conf'); + +module.exports = async (config, options, targetOptions) => { + const languagesHash = await hashElement(path.resolve(__dirname, '../src/main/webapp/i18n'), { + algo: 'md5', + encoding: 'hex', + files: { include: ['*.json'] }, + }); + + // PLUGINS + if (config.mode === 'development') { + config.plugins.push( + new ESLintPlugin({ + baseConfig: { + parserOptions: { + project: ['../tsconfig.app.json'], + }, + }, + }), + new WebpackNotifierPlugin({ + title: 'App', + contentImage: path.join(__dirname, 'logo-jhipster.png'), + }), + ); + } + + // configuring proxy for back end service + const tls = Boolean(config.devServer && config.devServer.https); + if (config.devServer) { + config.devServer.proxy = proxyConfig({ tls }); + } + + if (targetOptions.target === 'serve' || config.watch) { + config.plugins.push( + new BrowserSyncPlugin( + { + host: 'localhost', + port: 9000, + https: tls, + proxy: { + target: `http${tls ? 's' : ''}://localhost:${targetOptions.target === 'serve' ? '4200' : '8080'}`, + ws: true, + proxyOptions: { + changeOrigin: false, //pass the Host header to the backend unchanged https://github.com/Browsersync/browser-sync/issues/430 + }, + }, + socket: { + clients: { + heartbeatTimeout: 60000, + }, + }, + /* + ghostMode: { // uncomment this part to disable BrowserSync ghostMode; https://github.com/jhipster/generator-jhipster/issues/11116 + clicks: false, + location: false, + forms: false, + scroll: false, + }, + */ + }, + { + reload: targetOptions.target === 'build', // enabled for build --watch + }, + ), + ); + } + + if (config.mode === 'production') { + config.plugins.push( + new BundleAnalyzerPlugin({ + analyzerMode: 'static', + openAnalyzer: false, + // Webpack statistics in temporary folder + reportFilename: '../../stats.html', + }), + ); + } + + const patterns = [ + { + // https://github.com/swagger-api/swagger-ui/blob/v4.6.1/swagger-ui-dist-package/README.md + context: require('swagger-ui-dist').getAbsoluteFSPath(), + from: '*.{js,css,html,png}', + to: 'swagger-ui/', + globOptions: { ignore: ['**/index.html'] }, + }, + { + from: path.join(path.dirname(require.resolve('axios/package.json')), 'dist/axios.min.js'), + to: 'swagger-ui/', + }, + { from: './src/main/webapp/swagger-ui/', to: 'swagger-ui/' }, + // jhipster-needle-add-assets-to-webpack - JHipster will add/remove third-party resources in this array + ]; + + if (patterns.length > 0) { + config.plugins.push(new CopyWebpackPlugin({ patterns })); + } + + config.plugins.push( + new webpack.DefinePlugin({ + I18N_HASH: JSON.stringify(languagesHash.hash), + // APP_VERSION is passed as an environment variable from the Gradle / Maven build tasks. + __VERSION__: JSON.stringify(environment.__VERSION__), + __DEBUG_INFO_ENABLED__: environment.__DEBUG_INFO_ENABLED__ || config.mode === 'development', + // The root URL for API calls, ending with a '/' - for example: `"https://www.jhipster.tech:8081/myservice/"`. + // If this URL is left empty (""), then it will be relative to the current context. + // If you use an API server, in `prod` mode, you will need to enable CORS + // (see the `jhipster.cors` common JHipster property in the `application-*.yml` configurations) + SERVER_API_URL: JSON.stringify(environment.SERVER_API_URL), + }), + new MergeJsonWebpackPlugin({ + output: { + groupBy: [ + { pattern: './src/main/webapp/i18n/en/*.json', fileName: './i18n/en.json' }, + { pattern: './src/main/webapp/i18n/fr/*.json', fileName: './i18n/fr.json' }, + { pattern: './src/main/webapp/i18n/de/*.json', fileName: './i18n/de.json' }, + // jhipster-needle-i18n-language-webpack - JHipster will add/remove languages in this array + ], + }, + }), + ); + + config = merge( + config, + // jhipster-needle-add-webpack-config - JHipster will add custom config + ); + + return config; +};