diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9672f6dac..91462f325 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,9 +1,14 @@ -# References - + # Description - + + + # Validation performed - + diff --git a/.github/workflows/clp-core-build-macos.yaml b/.github/workflows/clp-core-build-macos.yaml index f353971e0..c46dfc878 100644 --- a/.github/workflows/clp-core-build-macos.yaml +++ b/.github/workflows/clp-core-build-macos.yaml @@ -32,7 +32,7 @@ jobs: build-macos: runs-on: "macos-12" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" diff --git a/.github/workflows/clp-core-build.yaml b/.github/workflows/clp-core-build.yaml index 9c3ad9109..d0dbbb28a 100644 --- a/.github/workflows/clp-core-build.yaml +++ b/.github/workflows/clp-core-build.yaml @@ -35,7 +35,7 @@ jobs: ubuntu_jammy_image_changed: "${{steps.filter.outputs.ubuntu_jammy_image}}" clp_changed: "${{steps.filter.outputs.clp}}" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -44,7 +44,7 @@ jobs: shell: "bash" - name: "Filter relevant changes" - uses: "dorny/paths-filter@v2" + uses: "dorny/paths-filter@v3" id: "filter" with: # Consider changes between the current commit and `main` @@ -91,7 +91,7 @@ jobs: needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -116,7 +116,7 @@ jobs: needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -141,7 +141,7 @@ jobs: needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -171,7 +171,7 @@ jobs: - "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -199,7 +199,7 @@ jobs: - "ubuntu-focal-deps-image" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -228,7 +228,7 @@ jobs: - "ubuntu-jammy-deps-image" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -261,7 +261,7 @@ jobs: OS_NAME: "ubuntu-focal" TMP_OUTPUT_DIR: "/tmp" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" diff --git a/.github/workflows/clp-docs.yaml b/.github/workflows/clp-docs.yaml new file mode 100644 index 000000000..2f0a68e77 --- /dev/null +++ b/.github/workflows/clp-docs.yaml @@ -0,0 +1,38 @@ +name: "clp-docs" + +on: + pull_request: + push: + workflow_dispatch: + +concurrency: + group: "${{github.workflow}}-${{github.ref}}" + # Cancel in-progress jobs for efficiency + cancel-in-progress: true + +jobs: + build: + strategy: + matrix: + os: ["macos-latest", "ubuntu-latest"] + runs-on: "${{matrix.os}}" + steps: + - uses: "actions/checkout@v4" + with: + submodules: "recursive" + + - uses: "actions/setup-python@v5" + with: + python-version: "3.10" + + - name: "Install task" + shell: "bash" + run: "npm install -g @go-task/cli" + + - if: "matrix.os == 'macos-latest'" + name: "Install coreutils (for md5sum)" + run: "brew install coreutils" + + - name: "Build docs" + shell: "bash" + run: "task docs:site" diff --git a/.github/workflows/clp-execution-image-build.yaml b/.github/workflows/clp-execution-image-build.yaml index c1fabea5d..d0bc5b017 100644 --- a/.github/workflows/clp-execution-image-build.yaml +++ b/.github/workflows/clp-execution-image-build.yaml @@ -25,7 +25,7 @@ jobs: ubuntu_focal_image_changed: "${{steps.filter.outputs.ubuntu_focal_image}}" ubuntu_jammy_image_changed: "${{steps.filter.outputs.ubuntu_jammy_image}}" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -34,7 +34,7 @@ jobs: shell: "bash" - name: "Filter relevant changes" - uses: "dorny/paths-filter@v2" + uses: "dorny/paths-filter@v3" id: "filter" with: base: "main" @@ -53,7 +53,7 @@ jobs: needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" @@ -74,7 +74,7 @@ jobs: needs: "filter-relevant-changes" runs-on: "ubuntu-latest" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" diff --git a/.github/workflows/clp-lint.yaml b/.github/workflows/clp-lint.yaml index dcb91ad94..75f74fe4a 100644 --- a/.github/workflows/clp-lint.yaml +++ b/.github/workflows/clp-lint.yaml @@ -20,7 +20,7 @@ jobs: os: ["macos-latest", "ubuntu-latest"] runs-on: "${{matrix.os}}" steps: - - uses: "actions/checkout@v3" + - uses: "actions/checkout@v4" with: submodules: "recursive" diff --git a/.gitmodules b/.gitmodules index 614f0871e..3935b0101 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,12 +14,18 @@ [submodule "components/core/submodules/log-surgeon"] path = components/core/submodules/log-surgeon url = https://github.com/y-scope/log-surgeon.git -[submodule "components/core/submodules/boost-outcome"] - path = components/core/submodules/boost-outcome - url = https://github.com/boostorg/outcome.git [submodule "components/core/submodules/simdjson"] path = components/core/submodules/simdjson url = https://github.com/simdjson/simdjson.git [submodule "components/core/submodules/abseil-cpp"] path = components/core/submodules/abseil-cpp url = https://github.com/abseil/abseil-cpp.git +[submodule "tools/yscope-dev-utils"] + path = tools/yscope-dev-utils + url = https://github.com/y-scope/yscope-dev-utils.git +[submodule "components/core/submodules/outcome"] + path = components/core/submodules/outcome + url = https://github.com/ned14/outcome.git +[submodule "components/log-viewer-webui/yscope-log-viewer"] + path = components/log-viewer-webui/yscope-log-viewer + url = https://github.com/y-scope/yscope-log-viewer.git diff --git a/Taskfile.yml b/Taskfile.yml index 256898d55..5b818c6e1 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,17 +3,22 @@ version: "3" includes: docs: "docs/tasks.yml" lint: "lint-tasks.yml" + utils: "tools/yscope-dev-utils/taskfiles/utils.yml" vars: # Paths G_BUILD_DIR: "{{.ROOT_DIR}}/build" G_CORE_COMPONENT_BUILD_DIR: "{{.G_BUILD_DIR}}/core" + G_LOG_VIEWER_WEBUI_BUILD_DIR: "{{.G_BUILD_DIR}}/log-viewer-webui" + G_LOG_VIEWER_WEBUI_SRC_DIR: "{{.ROOT_DIR}}/components/log-viewer-webui" G_METEOR_BUILD_DIR: "{{.G_BUILD_DIR}}/meteor" + G_NODEJS_14_BUILD_DIR: "{{.G_BUILD_DIR}}/nodejs-14" + G_NODEJS_14_BIN_DIR: "{{.G_NODEJS_14_BUILD_DIR}}/bin" + G_NODEJS_22_BUILD_DIR: "{{.G_BUILD_DIR}}/nodejs-22" + G_NODEJS_22_BIN_DIR: "{{.G_NODEJS_22_BUILD_DIR}}/bin" G_PACKAGE_BUILD_DIR: "{{.G_BUILD_DIR}}/clp-package" G_PACKAGE_VENV_DIR: "{{.G_BUILD_DIR}}/package-venv" G_WEBUI_BUILD_DIR: "{{.G_BUILD_DIR}}/webui" - G_WEBUI_NODEJS_BUILD_DIR: "{{.G_BUILD_DIR}}/webui-nodejs" - G_WEBUI_NODEJS_BIN_DIR: "{{.G_WEBUI_NODEJS_BUILD_DIR}}/bin" # Versions G_PACKAGE_VERSION: "0.2.0-dev" @@ -25,6 +30,7 @@ tasks: clean: cmds: - "rm -rf '{{.G_BUILD_DIR}}'" + - task: "clean-log-viewer-webui" - task: "clean-python-component" vars: COMPONENT: "clp-package-utils" @@ -34,6 +40,19 @@ tasks: - task: "clean-python-component" vars: COMPONENT: "job-orchestration" + - task: "clean-webui" + + clean-log-viewer-webui: + cmds: + - "rm -rf 'components/log-viewer-webui/client/node_modules'" + - "rm -rf 'components/log-viewer-webui/node_modules'" + - "rm -rf 'components/log-viewer-webui/server/node_modules'" + - "rm -rf 'components/log-viewer-webui/yscope-log-viewer/node_modules'" + + clean-webui: + cmds: + - "rm -rf 'components/webui/.meteor/local'" + - "rm -rf 'components/webui/node_modules'" clp-json-pkg-tar: cmds: @@ -50,22 +69,46 @@ tasks: STORAGE_ENGINE: "clp" package: + env: + NODE_ENV: "production" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_PACKAGE_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/log-viewer-webui-clients.md5" + - "{{.G_BUILD_DIR}}/package-venv.md5" + - "{{.G_BUILD_DIR}}/webui.md5" + - "{{.G_BUILD_DIR}}/webui-nodejs.md5" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clg" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clo" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" + - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package-lock.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/settings.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/src/**/*.js" + - "{{.TASKFILE}}" + - "/etc/os-release" + - "components/clp-package-utils/dist/*.whl" + - "components/clp-py-utils/dist/*.whl" + - "components/job-orchestration/dist/*.whl" + - "components/package-template/src/**/*" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "core" - "clp-package-utils" - "clp-py-utils" - "init" - "job-orchestration" + - "log-viewer-webui-clients" + - "nodejs-14" - "package-venv" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - "webui" - - "webui-nodejs" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - "rsync -a components/package-template/src/ '{{.OUTPUT_DIR}}'" @@ -86,50 +129,47 @@ tasks: "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" - "{{.G_WEBUI_NODEJS_BIN_DIR}}/node" "{{.OUTPUT_DIR}}/bin/" + - >- + rsync -a + "{{.G_NODEJS_14_BIN_DIR}}/node" + "{{.OUTPUT_DIR}}/bin/node-14" + - >- + rsync -a + "{{.G_NODEJS_22_BIN_DIR}}/node" + "{{.OUTPUT_DIR}}/bin/node-22" - "mkdir -p '{{.OUTPUT_DIR}}/var/www/'" - >- rsync -a "{{.G_WEBUI_BUILD_DIR}}/" - "{{.OUTPUT_DIR}}/var/www/" + "{{.OUTPUT_DIR}}/var/www/webui/" + # Avoid using `npm clean-install` because Meteor does not generate a `package-lock.json` file, + # which `clean-install` depends on. + - |- + cd "{{.OUTPUT_DIR}}/var/www/webui/programs/server" + PATH="{{.G_NODEJS_14_BIN_DIR}}":$PATH npm install + - >- + rsync -a + "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}/client" + "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}/yscope-log-viewer" + "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/" + - |- + cd components/log-viewer-webui/server/ + rsync -a \ + package.json package-lock.json settings.json src \ + "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/server/" - |- - cd "{{.OUTPUT_DIR}}/var/www/programs/server" - PATH="{{.G_WEBUI_NODEJS_BIN_DIR}}":$PATH npm install + cd "{{.OUTPUT_DIR}}/var/www/log_viewer_webui/server" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm clean-install # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/package-venv.md5" - - "{{.G_BUILD_DIR}}/webui.md5" - - "{{.G_BUILD_DIR}}/webui-nodejs.md5" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clg" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clo" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" - - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" - - "{{.TASKFILE}}" - - "/etc/os-release" - - "components/clp-package-utils/dist/*.whl" - - "components/clp-py-utils/dist/*.whl" - - "components/job-orchestration/dist/*.whl" - - "components/package-template/src/**/*" - generates: ["{{.CHECKSUM_FILE}}"] core: - deps: ["core-submodules", "init"] vars: SRC_DIR: "components/core" - cmds: - - "mkdir -p '{{.G_CORE_COMPONENT_BUILD_DIR}}'" - - "cmake -S '{{.SRC_DIR}}' -B '{{.G_CORE_COMPONENT_BUILD_DIR}}'" - - >- - cmake - --build "{{.G_CORE_COMPONENT_BUILD_DIR}}" - --parallel - --target clg clo clp clp-s reducer-server sources: - "{{.G_BUILD_DIR}}/core-submodules.md5" - "{{.SRC_DIR}}/cmake/**/*" @@ -143,6 +183,15 @@ tasks: - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp" - "{{.G_CORE_COMPONENT_BUILD_DIR}}/clp-s" - "{{.G_CORE_COMPONENT_BUILD_DIR}}/reducer-server" + deps: ["core-submodules", "init"] + cmds: + - "mkdir -p '{{.G_CORE_COMPONENT_BUILD_DIR}}'" + - "cmake -S '{{.SRC_DIR}}' -B '{{.G_CORE_COMPONENT_BUILD_DIR}}'" + - >- + cmake + --build "{{.G_CORE_COMPONENT_BUILD_DIR}}" + --parallel + --target clg clo clp clp-s reducer-server clp-package-utils: - task: "python-component" @@ -159,20 +208,71 @@ tasks: vars: COMPONENT: "{{.TASK}}" - webui: + log-viewer-webui-clients: + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + OUTPUT_DIR: "{{.G_LOG_VIEWER_WEBUI_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/log-viewer-webui-node-modules.md5" + - "{{.TASKFILE}}" + - "client/package.json" + - "client/src/**/*.css" + - "client/src/**/*.jsx" + - "client/src/webpack.config.js" + - "yscope-log-viewer/.babelrc" + - "yscope-log-viewer/customized-packages/**/*" + - "yscope-log-viewer/package.json" + - "yscope-log-viewer/src/**/*" + - "yscope-log-viewer/webpack.common.js" + - "yscope-log-viewer/webpack.prod.js" + dir: "components/log-viewer-webui" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - "meteor" - - task: "validate-checksum" + - "log-viewer-webui-node-modules" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - - "webui-node-modules" - dir: "components/webui" - platforms: ["386", "amd64"] + cmds: + - "rm -rf '{{.OUTPUT_DIR}}'" + - for: + - "client" + - "yscope-log-viewer" + cmd: |- + cd "{{.ITEM}}" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm run build -- \ + --output-path "{{.OUTPUT_DIR}}/{{.ITEM}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.OUTPUT_DIR}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" + + webui: vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_WEBUI_BUILD_DIR}}" + sources: + - "{{.G_BUILD_DIR}}/meteor.md5" + - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.TASKFILE}}" + - "*" + - ".meteor/*" + - "client/**/*" + - "imports/**/*" + - "server/**/*" + - "tests/**/*" + dir: "components/webui" + platforms: ["386", "amd64"] + generates: ["{{.CHECKSUM_FILE}}"] + deps: + - "init" + - "meteor" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.OUTPUT_DIR}}" + - "webui-node-modules" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - "mkdir -p '{{.OUTPUT_DIR}}'" @@ -187,27 +287,29 @@ tasks: # Remove temp files generated by `meteor build` before checksum - "find node_modules -type f -name '.meteor-portable-2.json' -exec rm {} +" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/meteor.md5" - - "{{.G_BUILD_DIR}}/webui-node-modules.md5" - - "{{.TASKFILE}}" - - "*" - - ".meteor/*" - - "client/**/*" - - "imports/**/*" - - "server/**/*" - - "tests/**/*" - generates: ["{{.CHECKSUM_FILE}}"] - webui-nodejs: + nodejs-22: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" - OUTPUT_DIR: "{{.G_WEBUI_NODEJS_BUILD_DIR}}" + OUTPUT_DIR: "{{.G_NODEJS_22_BUILD_DIR}}" + run: "once" + cmds: + - task: "nodejs" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + NODEJS_VERSION: "v22.4.0" + OUTPUT_DIR: "{{.OUTPUT_DIR}}" + + nodejs-14: + internal: true + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + OUTPUT_DIR: "{{.G_NODEJS_14_BUILD_DIR}}" cmds: - task: "nodejs" vars: @@ -217,41 +319,43 @@ tasks: core-submodules: internal: true - dir: "components/core" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "submodules" + sources: + - "{{.TASKFILE}}" + - ".gitmodules" + - "tools/scripts/deps-download/**/*" + dir: "components/core" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - "tools/scripts/deps-download/download-all.sh" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.TASKFILE}}" - - ".gitmodules" - - "tools/scripts/deps-download/**/*" - generates: ["{{.CHECKSUM_FILE}}"] download-and-extract-tar: internal: true - requires: - vars: ["CHECKSUM_FILE", "EXTRACTED_DIR_NAME", "TAR_NAME", "OUTPUT_DIR", "URL_PREFIX"] label: "{{.TASK}}-{{.TAR_NAME}}" vars: OUTPUT_TMP_DIR: "{{.OUTPUT_DIR}}-tmp" EXTRACTED_DIR: "{{.OUTPUT_TMP_DIR}}/{{.EXTRACTED_DIR_NAME}}" TAR_PATH: "{{.OUTPUT_TMP_DIR}}/{{.TAR_NAME}}" + requires: + vars: ["CHECKSUM_FILE", "EXTRACTED_DIR_NAME", "TAR_NAME", "OUTPUT_DIR", "URL_PREFIX"] + sources: ["{{.TASKFILE}}"] + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -266,24 +370,140 @@ tasks: - "mv '{{.EXTRACTED_DIR}}' '{{.OUTPUT_DIR}}'" - "rm -rf '{{.OUTPUT_TMP_DIR}}'" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: ["{{.TASKFILE}}"] + + # NOTE: The log-viewer-webui has four different node_modules directories: + # * client + # * server + # * log-viewer submodule + # * the top-level one we call "package" + # This means we have to create four different checksums. To allow tasks which depend on this task + # to only have to check one checksum file, we concatenate the four checksum files into one. + log-viewer-webui-node-modules: + internal: true + vars: + # Checksum files + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + CLIENT_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-client-node-modules.md5" + LOG_VIEWER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-log-viewer-node-modules.md5" + PACKAGE_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-package-node-modules.md5" + SERVER_CHECKSUM_FILE: "{{.G_BUILD_DIR}}/log-viewer-webui-server-node-modules.md5" + + # Directories + SRC_DIR: "{{.TASKFILE_DIR}}/components/log-viewer-webui" + CLIENT_OUTPUT_DIR: "{{.SRC_DIR}}/client/node_modules" + LOG_VIEWER_OUTPUT_DIR: "{{.SRC_DIR}}/yscope-log-viewer/node_modules" + PACKAGE_OUTPUT_DIR: "{{.SRC_DIR}}/node_modules" + SERVER_OUTPUT_DIR: "{{.SRC_DIR}}/server/node_modules" + sources: + - "{{.G_BUILD_DIR}}/log-viewer-webui-submodules.md5" + - "{{.G_BUILD_DIR}}/nodejs-22.md5" + - "{{.TASKFILE}}" + - "client/package.json" + - "client/package-lock.json" + - "package.json" + - "package-lock.json" + - "server/package.json" + - "server/package-lock.json" + - "yscope-log-viewer/package.json" + - "yscope-log-viewer/package-lock.json" + dir: "{{.SRC_DIR}}" + generates: + - "{{.CHECKSUM_FILE}}" + - "{{.CLIENT_CHECKSUM_FILE}}" + - "{{.LOG_VIEWER_CHECKSUM_FILE}}" + - "{{.PACKAGE_CHECKSUM_FILE}}" + - "{{.SERVER_CHECKSUM_FILE}}" + deps: + - "log-viewer-webui-submodules" + - "nodejs-22" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CLIENT_CHECKSUM_FILE}}" + DATA_DIR: "{{.CLIENT_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.SERVER_CHECKSUM_FILE}}" + DATA_DIR: "{{.SERVER_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.PACKAGE_CHECKSUM_FILE}}" + DATA_DIR: "{{.PACKAGE_OUTPUT_DIR}}" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.LOG_VIEWER_CHECKSUM_FILE}}" + DATA_DIR: "{{.LOG_VIEWER_OUTPUT_DIR}}" + cmds: + - "rm -f {{.CHECKSUM_FILE}}" + - task: "clean-log-viewer-webui" + - "PATH='{{.G_NODEJS_22_BIN_DIR}}':$PATH npm run init" + - |- + cd yscope-log-viewer + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm install + # These commands must be last + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.CLIENT_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.CLIENT_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.LOG_VIEWER_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.LOG_VIEWER_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.PACKAGE_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.PACKAGE_CHECKSUM_FILE}}" + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.SERVER_OUTPUT_DIR}}" + OUTPUT_FILE: "{{.SERVER_CHECKSUM_FILE}}" + # This command must be last + - >- + cat + "{{.CLIENT_CHECKSUM_FILE}}" + "{{.LOG_VIEWER_CHECKSUM_FILE}}" + "{{.PACKAGE_CHECKSUM_FILE}}" + "{{.SERVER_CHECKSUM_FILE}}" + > "{{.CHECKSUM_FILE}}" + + log-viewer-webui-submodules: + internal: true + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + OUTPUT_DIR: "yscope-log-viewer" + sources: + - "{{.TASKFILE}}" + - ".gitmodules" + dir: "components/log-viewer-webui" generates: ["{{.CHECKSUM_FILE}}"] + deps: + - "init" + - task: "utils:validate-checksum" + vars: + CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" + DATA_DIR: "{{.OUTPUT_DIR}}" + cmds: + - "git submodule update --init --recursive yscope-log-viewer" + # This command must be last + - task: "utils:compute-checksum" + vars: + DATA_DIR: "{{.OUTPUT_DIR}}" + OUTPUT_FILE: "{{.CHECKSUM_FILE}}" meteor: + vars: + CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" + METEOR_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x86_64{{ end }}" + METEOR_PLATFORM: "{{ if eq OS \"darwin\" }}osx{{ else }}linux{{ end }}" + METEOR_RELEASE: "2.16" run: "once" preconditions: - sh: >- (test "$(uname -m)" != "aarch64") || (test "$(uname -s)" != "Linux") msg: "Meteor 2.x does not support aarch64 on Linux" - vars: - CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" - METEOR_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x86_64{{ end }}" - METEOR_PLATFORM: "{{ if eq OS \"darwin\" }}osx{{ else }}linux{{ end }}" - METEOR_RELEASE: "2.15" cmds: - task: "download-and-extract-tar" vars: @@ -295,9 +515,6 @@ tasks: nodejs: internal: true - deps: ["init"] - requires: - vars: ["CHECKSUM_FILE", "NODEJS_VERSION", "OUTPUT_DIR"] vars: NODEJS_ARCH: "{{ if eq ARCH \"arm64\" }}arm64{{ else }}x64{{ end }}" NODEJS_VERSION_BASE_URL: "https://nodejs.org/dist/{{.NODEJS_VERSION}}/" @@ -309,6 +526,9 @@ tasks: --max-count 1 "node-v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+-{{OS}}-{{.NODEJS_ARCH}}" | head --lines 1 + requires: + vars: ["CHECKSUM_FILE", "NODEJS_VERSION", "OUTPUT_DIR"] + deps: ["init"] cmds: - task: "download-and-extract-tar" vars: @@ -320,16 +540,19 @@ tasks: package-tar: internal: true - requires: - vars: ["FLAVOUR", "STORAGE_ENGINE"] vars: VERSIONED_PACKAGE_NAME: - sh: | - source /etc/os-release - echo "clp-{{.FLAVOUR}}-${ID}-${VERSION_CODENAME}-$(arch)-v{{.G_PACKAGE_VERSION}}" + sh: "echo clp-{{.FLAVOUR}}-$(arch)-v{{.G_PACKAGE_VERSION}}" OUTPUT_DIR: "{{.G_BUILD_DIR}}/{{.VERSIONED_PACKAGE_NAME}}" OUTPUT_FILE: "{{.OUTPUT_DIR}}.tar.gz" + requires: + vars: ["FLAVOUR", "STORAGE_ENGINE"] + sources: + - "{{.G_BUILD_DIR}}/package.md5" + - "{{.TASKFILE}}" dir: "{{.G_BUILD_DIR}}" + generates: + - "{{.VERSIONED_PACKAGE_NAME}}.tar.gz" deps: ["package"] cmds: - "rm -rf '{{.OUTPUT_DIR}}' '{{.OUTPUT_FILE}}'" @@ -337,12 +560,12 @@ tasks: # `/parents/A` -> `/parents/B` rather than `/parents/A` -> `/parents/B/A` - "rsync --archive '{{.G_PACKAGE_BUILD_DIR}}/' '{{.OUTPUT_DIR}}'" # Set the storage engine for the package - - task: "replace-text" + - task: "utils:replace-text" vars: FILE_PATH: "{{.OUTPUT_DIR}}/lib/python3/site-packages/clp_py_utils/clp_config.py" SED_EXP: >- s/([[:space:]]*storage_engine: str = ")[^"]+"/\1{{.STORAGE_ENGINE}}"/ - - task: "replace-text" + - task: "utils:replace-text" vars: FILE_PATH: "{{.OUTPUT_DIR}}/etc/clp-config.yml" SED_EXP: >- @@ -351,62 +574,44 @@ tasks: tar czf '{{.OUTPUT_FILE}}' --directory '{{.G_BUILD_DIR}}' --dereference '{{.VERSIONED_PACKAGE_NAME}}' - sources: - - "{{.G_BUILD_DIR}}/package.md5" - - "{{.TASKFILE}}" - generates: - - "{{.VERSIONED_PACKAGE_NAME}}.tar.gz" package-venv: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" OUTPUT_DIR: "{{.G_PACKAGE_VENV_DIR}}" + sources: + - "{{.ROOT_DIR}}/requirements.txt" + - "{{.TASKFILE}}" + - "/etc/os-release" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: "create-venv" + - task: "utils:create-venv" vars: LABEL: "package" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "requirements.txt" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/requirements.txt" - - "{{.TASKFILE}}" - - "/etc/os-release" - generates: ["{{.CHECKSUM_FILE}}"] python-component: internal: true - requires: - vars: ["COMPONENT"] label: "{{.COMPONENT}}" - deps: - - task: "component-venv" - vars: - COMPONENT: "{{.COMPONENT}}" - OUTPUT_DIR: "{{.VENV_DIR}}" vars: PACKAGE: sh: "echo {{.COMPONENT}} | tr - _" VENV_DIR: "{{.G_BUILD_DIR}}/{{.COMPONENT}}/venv" - dir: "components/{{.COMPONENT}}" - cmds: - - task: "clean-python-component" - vars: - COMPONENT: "{{.COMPONENT}}" - - |- - . "{{.VENV_DIR}}/bin/activate" - poetry build --format wheel + requires: + vars: ["COMPONENT"] sources: - "{{.G_BUILD_DIR}}/{{.COMPONENT}}_venv.md5" - "{{.PACKAGE}}/**/*" @@ -414,8 +619,21 @@ tasks: - "{{.TASKFILE}}" - "/etc/os-release" - "pyproject.toml" + dir: "components/{{.COMPONENT}}" generates: - "dist/*.whl" + deps: + - task: "component-venv" + vars: + COMPONENT: "{{.COMPONENT}}" + OUTPUT_DIR: "{{.VENV_DIR}}" + cmds: + - task: "clean-python-component" + vars: + COMPONENT: "{{.COMPONENT}}" + - |- + . "{{.VENV_DIR}}/bin/activate" + poetry build --format wheel webui-node-modules: internal: true @@ -423,11 +641,17 @@ tasks: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK}}.md5" WEBUI_SRC_DIR: "{{.ROOT_DIR}}/components/webui" OUTPUT_DIR: "{{.WEBUI_SRC_DIR}}/node_modules" + sources: + - "{{.G_BUILD_DIR}}/meteor.md5" + - "{{.TASKFILE}}" + - ".meteor/packages" + - "package.json" dir: "{{.WEBUI_SRC_DIR}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - "meteor" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -435,139 +659,53 @@ tasks: - "rm -rf '{{.OUTPUT_DIR}}'" - "PATH='{{.G_METEOR_BUILD_DIR}}':$PATH meteor npm install --production" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/meteor.md5" - - "{{.TASKFILE}}" - - ".meteor/packages" - - "package.json" - generates: ["{{.CHECKSUM_FILE}}"] component-venv: internal: true - requires: - vars: ["COMPONENT", "OUTPUT_DIR"] label: "{{.COMPONENT}}-venv" - dir: "components/{{.COMPONENT}}" vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.COMPONENT}}-venv.md5" + requires: + vars: ["COMPONENT", "OUTPUT_DIR"] + sources: + - "{{.ROOT_DIR}}/requirements.txt" + - "{{.TASKFILE}}" + - "/etc/os-release" + - "pyproject.toml" + dir: "components/{{.COMPONENT}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - "init" - - task: "validate-checksum" + - task: "utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: "create-venv" + - task: "utils:create-venv" vars: LABEL: "{{.COMPONENT}}" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "{{.ROOT_DIR}}/requirements.txt" # This command must be last - - task: "compute-checksum" + - task: "utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/requirements.txt" - - "{{.TASKFILE}}" - - "/etc/os-release" - - "pyproject.toml" - generates: ["{{.CHECKSUM_FILE}}"] - clean-python-component: internal: true + label: "clean-{{.COMPONENT}}" requires: vars: ["COMPONENT"] - label: "clean-{{.COMPONENT}}" dir: "components/{{.COMPONENT}}" cmds: - "rm -rf dist" - create-venv: - internal: true - requires: - vars: ["LABEL", "OUTPUT_DIR", "REQUIREMENTS_FILE"] - label: "create-venv-{{.LABEL}}" - cmds: - - "rm -rf '{{.OUTPUT_DIR}}'" - - "python3 -m venv '{{.OUTPUT_DIR}}'" - # Remove calls to `hash` from the venv activation script since Task uses `gosh` rather than - # `bash`. - # NOTE: Older versions of Python's venv would only call `hash` if they detected the running - # shell was one that had the command, but that's not the case in newer versions. - - task: "replace-text" - vars: - FILE_PATH: "{{.OUTPUT_DIR}}/bin/activate" - SED_EXP: >- - s/^([[:space:]]*)hash[[:space:]]+.*/\1true/g - - |- - . "{{.OUTPUT_DIR}}/bin/activate" - pip3 install --upgrade pip - pip3 install --upgrade -r "{{.REQUIREMENTS_FILE}}" - init: internal: true - run: "once" silent: true + run: "once" cmd: "mkdir -p '{{.G_BUILD_DIR}}'" - - compute-checksum: - desc: "Tries to compute a checksum for the given directory and output it to a file." - internal: true - # Ignore errors so that dependent tasks don't fail - ignore_error: true - silent: true - requires: - vars: ["DATA_DIR", "OUTPUT_FILE"] - cmds: - - >- - tar cf - - --directory "{{.DATA_DIR}}" - --group=0 - --mtime='UTC 1970-01-01' - --numeric-owner - --owner=0 - --sort=name - {{.CHECKSUM_TAR_BASE_ARGS}} . 2> /dev/null - | md5sum > {{.OUTPUT_FILE}} - - validate-checksum: - desc: "Validates the checksum of the given directory matches the checksum in the given file, or - deletes the checksum file otherwise." - internal: true - silent: true - requires: - vars: ["CHECKSUM_FILE", "DATA_DIR"] - vars: - TMP_CHECKSUM_FILE: "{{.CHECKSUM_FILE}}.tmp" - cmds: - - task: "compute-checksum" - vars: - DATA_DIR: "{{.DATA_DIR}}" - OUTPUT_FILE: "{{.TMP_CHECKSUM_FILE}}" - - defer: "rm -f '{{.TMP_CHECKSUM_FILE}}'" - # Check that the directory exists and the checksum matches; otherwise delete the checksum file - - >- - ( - test -d "{{.DATA_DIR}}" - && diff -q '{{.TMP_CHECKSUM_FILE}}' '{{.CHECKSUM_FILE}}' 2> /dev/null - ) || rm -f '{{.CHECKSUM_FILE}}' - - replace-text: - desc: "Task to replace some text in a file using sed." - internal: true - requires: - vars: ["FILE_PATH", "SED_EXP"] - cmds: - - |- - # NOTE: - # 1. We can't use `sed -i` since `-i` has different syntax on Linux and macOS - # 2. We can't use `--regexp` instead of `-E` since `--regexp` is not supported on macOS - src="{{.FILE_PATH}}" - dst="{{.FILE_PATH}}.tmp" - sed -E '{{.SED_EXP}}' "${src}" > "${dst}" - mv "${dst}" "${src}" diff --git a/components/clp-package-utils/clp_package_utils/general.py b/components/clp-package-utils/clp_package_utils/general.py index 29c421109..4dca481b0 100644 --- a/components/clp-package-utils/clp_package_utils/general.py +++ b/components/clp-package-utils/clp_package_utils/general.py @@ -6,12 +6,16 @@ import socket import subprocess import typing +import uuid +from enum import auto +from typing import List, Optional, Tuple import yaml from clp_py_utils.clp_config import ( CLP_DEFAULT_CREDENTIALS_FILE_PATH, CLPConfig, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, @@ -24,8 +28,12 @@ read_yaml_config_file, validate_path_could_be_dir, ) +from strenum import KebabCaseStrEnum # CONSTANTS +EXTRACT_FILE_CMD = "x" +EXTRACT_IR_CMD = "i" + # Paths CONTAINER_CLP_HOME = pathlib.Path("/") / "opt" / "clp" CONTAINER_INPUT_LOGS_ROOT_DIR = pathlib.Path("/") / "mnt" / "logs" @@ -38,6 +46,13 @@ class DockerMountType(enum.IntEnum): BIND = 0 +class JobType(KebabCaseStrEnum): + COMPRESSION = auto() + FILE_EXTRACTION = auto() + IR_EXTRACTION = auto() + SEARCH = auto() + + class DockerMount: def __init__( self, @@ -69,6 +84,7 @@ def __init__(self, clp_home: pathlib.Path, docker_clp_home: pathlib.Path): self.data_dir: typing.Optional[DockerMount] = None self.logs_dir: typing.Optional[DockerMount] = None self.archives_output_dir: typing.Optional[DockerMount] = None + self.ir_output_dir: typing.Optional[DockerMount] = None def get_clp_home(): @@ -90,6 +106,14 @@ def get_clp_home(): return clp_home.resolve() +def generate_container_name(job_type: JobType) -> str: + """ + :param job_type: + :return: A unique container name for the given job type. + """ + return f"clp-{job_type}-{str(uuid.uuid4())[-4:]}" + + def check_dependencies(): try: subprocess.run( @@ -176,12 +200,15 @@ def is_path_already_mounted( return host_path_relative_to_mounted_root == container_path_relative_to_mounted_root -def generate_container_config(clp_config: CLPConfig, clp_home: pathlib.Path): +def generate_container_config( + clp_config: CLPConfig, clp_home: pathlib.Path +) -> Tuple[CLPConfig, CLPDockerMounts]: """ Copies the given config and sets up mounts mapping the relevant host paths into the container :param clp_config: :param clp_home: + :return: The container config and the mounts. """ container_clp_config = clp_config.copy(deep=True) @@ -224,9 +251,73 @@ def generate_container_config(clp_config: CLPConfig, clp_home: pathlib.Path): container_clp_config.archive_output.directory, ) + container_clp_config.ir_output.directory = pathlib.Path("/") / "mnt" / "ir-output" + if not is_path_already_mounted( + clp_home, + CONTAINER_CLP_HOME, + clp_config.ir_output.directory, + container_clp_config.ir_output.directory, + ): + docker_mounts.ir_output_dir = DockerMount( + DockerMountType.BIND, + clp_config.ir_output.directory, + container_clp_config.ir_output.directory, + ) + return container_clp_config, docker_mounts +def dump_container_config( + container_clp_config: CLPConfig, clp_config: CLPConfig, container_name: str +) -> Tuple[pathlib.Path, pathlib.Path]: + """ + Writes the given config to the logs directory so that it's accessible in the container. + :param container_clp_config: The config to write. + :param clp_config: The corresponding config on the host (used to determine the logs directory). + :param container_name: + :return: The path to the config file in the container and on the host. + """ + container_config_filename = f".{container_name}-config.yml" + config_file_path_on_host = clp_config.logs_directory / container_config_filename + config_file_path_on_container = container_clp_config.logs_directory / container_config_filename + with open(config_file_path_on_host, "w") as f: + yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + + return config_file_path_on_container, config_file_path_on_host + + +def generate_container_start_cmd( + container_name: str, container_mounts: List[Optional[DockerMount]], container_image: str +) -> List[str]: + """ + Generates the command to start a container with the given mounts and name. + :param container_name: + :param container_mounts: + :param container_image: + :return: The command. + """ + clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" + # fmt: off + container_start_cmd = [ + "docker", "run", + "-i", + "--rm", + "--network", "host", + "-w", str(CONTAINER_CLP_HOME), + "-e", f"PYTHONPATH={clp_site_packages_dir}", + "-u", f"{os.getuid()}:{os.getgid()}", + "--name", container_name, + "--log-driver", "local" + ] + for mount in container_mounts: + if mount: + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) + container_start_cmd.append(container_image) + + return container_start_cmd + + def validate_config_key_existence(config, key): try: value = get_config_value(config, key) @@ -235,7 +326,7 @@ def validate_config_key_existence(config, key): return value -def validate_and_load_config_file( +def load_config_file( config_file_path: pathlib.Path, default_config_file_path: pathlib.Path, clp_home: pathlib.Path ): if config_file_path.exists(): @@ -391,6 +482,7 @@ def validate_results_cache_config( def validate_worker_config(clp_config: CLPConfig): clp_config.validate_input_logs_dir() clp_config.validate_archive_output_dir() + clp_config.validate_ir_output_dir() def validate_webui_config( @@ -407,3 +499,16 @@ def validate_webui_config( raise ValueError(f"{WEBUI_COMPONENT_NAME} logs directory is invalid: {ex}") validate_port(f"{WEBUI_COMPONENT_NAME}.port", clp_config.webui.host, clp_config.webui.port) + + +def validate_log_viewer_webui_config(clp_config: CLPConfig, settings_json_path: pathlib.Path): + if not settings_json_path.exists(): + raise ValueError( + f"{WEBUI_COMPONENT_NAME} {settings_json_path} is not a valid path to settings.json" + ) + + validate_port( + f"{LOG_VIEWER_WEBUI_COMPONENT_NAME}.port", + clp_config.log_viewer_webui.host, + clp_config.log_viewer_webui.port, + ) diff --git a/components/clp-package-utils/clp_package_utils/scripts/compress.py b/components/clp-package-utils/clp_package_utils/scripts/compress.py index 61495a4cd..d0aa30913 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/compress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/compress.py @@ -1,20 +1,20 @@ import argparse import logging -import os import pathlib import subprocess import sys import uuid -import yaml - from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, CONTAINER_INPUT_LOGS_ROOT_DIR, + dump_container_config, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, ) @@ -57,51 +57,32 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() + # Validate and load necessary credentials validate_and_load_db_credentials_file(clp_config, clp_home, False) except: logger.exception("Failed to load config.") return -1 - container_name = f"clp-compressor-{str(uuid.uuid4())[-4:]}" + container_name = generate_container_name(JobType.COMPRESSION) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on - necessary_mounts = [mounts.input_logs_dir, mounts.data_dir, mounts.logs_dir] - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - container_start_cmd.append(clp_config.execution_container) + necessary_mounts = [mounts.clp_home, mounts.input_logs_dir, mounts.data_dir, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off compress_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.compress", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), "--remove-path-prefix", str(CONTAINER_INPUT_LOGS_ROOT_DIR), ] # fmt: on @@ -140,7 +121,7 @@ def main(argv): subprocess.run(cmd, check=True) # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 diff --git a/components/clp-package-utils/clp_package_utils/scripts/decompress.py b/components/clp-package-utils/clp_package_utils/scripts/decompress.py index f20291b50..1a2973fec 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/decompress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/decompress.py @@ -1,21 +1,25 @@ import argparse import logging -import os import pathlib import subprocess import sys -import uuid +from typing import Optional -import yaml +from clp_py_utils.clp_config import CLPConfig from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, DockerMount, DockerMountType, + dump_container_config, + EXTRACT_FILE_CMD, + EXTRACT_IR_CMD, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, validate_path_could_be_dir, ) @@ -31,41 +35,43 @@ logger.addHandler(logging_console_handler) -def main(argv): - clp_home = get_clp_home() - default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH - - args_parser = argparse.ArgumentParser(description="Decompresses logs") - args_parser.add_argument( - "--config", - "-c", - type=str, - default=str(default_config_file_path), - help="CLP package configuration file.", - ) - args_parser.add_argument("paths", metavar="PATH", nargs="*", help="Files to decompress.") - args_parser.add_argument("-f", "--files-from", help="A file listing all files to decompress.") - args_parser.add_argument( - "-d", "--extraction-dir", metavar="DIR", default=".", help="Decompress files into DIR" - ) - parsed_args = args_parser.parse_args(argv[1:]) - - # Validate and load config file +def validate_and_load_config( + clp_home: pathlib.Path, + config_file_path: pathlib.Path, + default_config_file_path: pathlib.Path, +) -> Optional[CLPConfig]: + """ + Validates and loads the config file. + :param clp_home: + :param config_file_path: + :param default_config_file_path: + :return: The config object on success, None otherwise. + """ try: - config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() + # Validate and load necessary credentials validate_and_load_db_credentials_file(clp_config, clp_home, False) + return clp_config except: logger.exception("Failed to load config.") - return -1 + return None + - paths_to_decompress_file_path = None +def handle_extract_file_cmd( + parsed_args, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the file extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + paths_to_extract_file_path = None if parsed_args.files_from: - paths_to_decompress_file_path = pathlib.Path(parsed_args.files_from) + paths_to_extract_file_path = pathlib.Path(parsed_args.files_from) # Validate extraction directory extraction_dir = pathlib.Path(parsed_args.extraction_dir).resolve() @@ -74,81 +80,178 @@ def main(argv): except ValueError as ex: logger.error(f"extraction-dir is invalid: {ex}") return -1 - extraction_dir.mkdir(exist_ok=True) - container_name = f"clp-decompressor-{str(uuid.uuid4())[-4:]}" + # Validate and load config file + clp_config = validate_and_load_config( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + container_name = generate_container_name(JobType.FILE_EXTRACTION) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) - - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) # Set up mounts + extraction_dir.mkdir(exist_ok=True) container_extraction_dir = pathlib.Path("/") / "mnt" / "extraction-dir" necessary_mounts = [ + mounts.clp_home, mounts.data_dir, mounts.logs_dir, mounts.archives_output_dir, DockerMount(DockerMountType.BIND, extraction_dir, container_extraction_dir), ] - container_paths_to_decompress_file_path = None - if paths_to_decompress_file_path: - container_paths_to_decompress_file_path = ( - pathlib.Path("/") / "mnt" / "paths-to-decompress.txt" - ) + container_paths_to_extract_file_path = None + if paths_to_extract_file_path: + container_paths_to_extract_file_path = pathlib.Path("/") / "mnt" / "paths-to-extract.txt" necessary_mounts.append( DockerMount( DockerMountType.BIND, - paths_to_decompress_file_path, - container_paths_to_decompress_file_path, + paths_to_extract_file_path, + container_paths_to_extract_file_path, ) ) - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - - container_start_cmd.append(clp_config.execution_container) + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off - decompress_cmd = [ + extract_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.decompress", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), + EXTRACT_FILE_CMD, "-d", str(container_extraction_dir), ] # fmt: on for path in parsed_args.paths: - decompress_cmd.append(path) - if container_paths_to_decompress_file_path: - decompress_cmd.append("--input-list") - decompress_cmd.append(container_paths_to_decompress_file_path) + extract_cmd.append(path) + if container_paths_to_extract_file_path: + extract_cmd.append("--input-list") + extract_cmd.append(container_paths_to_extract_file_path) + + cmd = container_start_cmd + extract_cmd + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError: + logger.exception("Docker or file extraction command failed.") + return -1 + + # Remove generated files + generated_config_path_on_host.unlink() + + return 0 + - cmd = container_start_cmd + decompress_cmd - subprocess.run(cmd, check=True) +def handle_extract_ir_cmd( + parsed_args, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the IR extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate and load config file + clp_config = validate_and_load_config( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + container_name = generate_container_name(JobType.IR_EXTRACTION) + container_clp_config, mounts = generate_container_config(clp_config, clp_home) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) + necessary_mounts = [mounts.clp_home, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) + + # fmt: off + extract_cmd = [ + "python3", + "-m", "clp_package_utils.scripts.native.decompress", + "--config", str(generated_config_path_on_container), + EXTRACT_IR_CMD, + str(parsed_args.msg_ix), + ] + # fmt: on + if parsed_args.orig_file_id: + extract_cmd.append("--orig-file-id") + extract_cmd.append(str(parsed_args.orig_file_id)) + else: + extract_cmd.append("--orig-file-path") + extract_cmd.append(str(parsed_args.orig_file_path)) + if parsed_args.target_uncompressed_size: + extract_cmd.append("--target-uncompressed-size") + extract_cmd.append(str(parsed_args.target_uncompressed_size)) + cmd = container_start_cmd + extract_cmd + + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError: + logger.exception("Docker or IR extraction command failed.") + return -1 # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 +def main(argv): + clp_home = get_clp_home() + default_config_file_path = clp_home / CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH + + args_parser = argparse.ArgumentParser(description="Decompresses logs") + args_parser.add_argument( + "--config", + "-c", + default=str(default_config_file_path), + help="CLP configuration file.", + ) + command_args_parser = args_parser.add_subparsers(dest="command", required=True) + + # File extraction command parser + file_extraction_parser = command_args_parser.add_parser(EXTRACT_FILE_CMD) + file_extraction_parser.add_argument( + "paths", metavar="PATH", nargs="*", help="Files to extract." + ) + file_extraction_parser.add_argument( + "-f", "--files-from", help="A file listing all files to extract." + ) + file_extraction_parser.add_argument( + "-d", "--extraction-dir", metavar="DIR", default=".", help="Extract files into DIR." + ) + + # IR extraction command parser + ir_extraction_parser = command_args_parser.add_parser(EXTRACT_IR_CMD) + ir_extraction_parser.add_argument("msg_ix", type=int, help="Message index.") + ir_extraction_parser.add_argument( + "--target-uncompressed-size", type=int, help="Target uncompressed IR size." + ) + + group = ir_extraction_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--orig-file-id", type=str, help="Original file's ID.") + group.add_argument("--orig-file-path", type=str, help="Original file's path.") + + parsed_args = args_parser.parse_args(argv[1:]) + + command = parsed_args.command + if EXTRACT_FILE_CMD == command: + return handle_extract_file_cmd(parsed_args, clp_home, default_config_file_path) + elif EXTRACT_IR_CMD == command: + return handle_extract_ir_cmd(parsed_args, clp_home, default_config_file_path) + else: + logger.exception(f"Unexpected command: {command}") + return -1 + + if "__main__" == __name__: sys.exit(main(sys.argv)) diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/compress.py b/components/clp-package-utils/clp_package_utils/scripts/native/compress.py index a08602007..cb495204f 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/compress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/compress.py @@ -20,7 +20,7 @@ from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, get_clp_home, - validate_and_load_config_file, + load_config_file, ) # Setup logging @@ -170,9 +170,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_input_logs_dir() clp_config.validate_logs_dir() except: diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py b/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py index 9ca3ab7b6..b6585b192 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/decompress.py @@ -1,17 +1,30 @@ import argparse +import asyncio import logging import pathlib import subprocess import sys import uuid +from contextlib import closing +from typing import Optional import yaml -from clp_py_utils.clp_config import CLPConfig +from clp_py_utils.clp_config import CLP_METADATA_TABLE_PREFIX, CLPConfig, Database +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.job_config import ExtractIrJobConfig from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, + EXTRACT_FILE_CMD, + EXTRACT_IR_CMD, get_clp_home, - validate_and_load_config_file, + load_config_file, +) +from clp_package_utils.scripts.native.utils import ( + run_function_in_process, + submit_query_job, + wait_for_query_job, ) # Setup logging @@ -25,50 +38,204 @@ logger.addHandler(logging_console_handler) -def decompress_paths( +def get_orig_file_id(db_config: Database, path: str) -> Optional[str]: + """ + :param db_config: + :param path: Path of the original file. + :return: The ID of an original file which has the given path, or None if no such file exists. + NOTE: Multiple original files may have the same path in which case this method returns the ID of + only one of them. + """ + sql_adapter = SQL_Adapter(db_config) + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + db_cursor.execute( + f"SELECT orig_file_id FROM `{CLP_METADATA_TABLE_PREFIX}files` WHERE path = (%s)", + (path,), + ) + results = db_cursor.fetchall() + db_conn.commit() + + if len(results) == 0: + logger.error("No file found for the given path.") + return None + + if len(results) > 1: + logger.warning( + "Multiple files found for the given path." + " Returning the orig_file_id of one of them." + ) + + return results[0]["orig_file_id"] + + +def submit_and_monitor_ir_extraction_job_in_db( + db_config: Database, + orig_file_id: str, + msg_ix: int, + target_uncompressed_size: Optional[int], +) -> int: + """ + Submits an IR extraction job to the scheduler and waits until the job finishes. + :param db_config: + :param orig_file_id: + :param msg_ix: + :param target_uncompressed_size: + :return: 0 on success, -1 otherwise. + """ + extract_ir_config = ExtractIrJobConfig( + orig_file_id=orig_file_id, + msg_ix=msg_ix, + target_uncompressed_size=target_uncompressed_size, + ) + + sql_adapter = SQL_Adapter(db_config) + job_id = submit_query_job(sql_adapter, extract_ir_config, QueryJobType.EXTRACT_IR) + job_status = wait_for_query_job(sql_adapter, job_id) + + if QueryJobStatus.SUCCEEDED == job_status: + logger.info(f"Finished IR extraction job {job_id}.") + return 0 + + logger.error( + f"IR extraction job {job_id} finished with unexpected status: {job_status.to_str()}." + ) + return -1 + + +def handle_extract_ir_cmd( + parsed_args: argparse.Namespace, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the IR extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate and load config file + clp_config = validate_and_load_config_file( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + orig_file_id: str + if parsed_args.orig_file_id: + orig_file_id = parsed_args.orig_file_id + else: + orig_file_id = get_orig_file_id(clp_config.database, parsed_args.orig_file_path) + if orig_file_id is None: + return -1 + + try: + return asyncio.run( + run_function_in_process( + submit_and_monitor_ir_extraction_job_in_db, + clp_config.database, + orig_file_id, + parsed_args.msg_ix, + parsed_args.target_uncompressed_size, + ) + ) + except asyncio.CancelledError: + logger.error("IR extraction cancelled.") + return -1 + + +def validate_and_load_config_file( clp_home: pathlib.Path, - paths, - list_path: pathlib.Path, - clp_config: CLPConfig, - archives_dir: pathlib.Path, - logs_dir: pathlib.Path, - extraction_dir: pathlib.Path, -): + config_file_path: pathlib.Path, + default_config_file_path: pathlib.Path, +) -> Optional[CLPConfig]: + """ + Validates and loads the config file. + :param clp_home: + :param config_file_path: + :param default_config_file_path: + :return: clp_config on success, None otherwise. + """ + try: + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) + clp_config.validate_archive_output_dir() + clp_config.validate_logs_dir() + return clp_config + except Exception: + logger.exception("Failed to load config.") + return None + + +def handle_extract_file_cmd( + parsed_args: argparse.Namespace, clp_home: pathlib.Path, default_config_file_path: pathlib.Path +) -> int: + """ + Handles the file extraction command. + :param parsed_args: + :param clp_home: + :param default_config_file_path: + :return: 0 on success, -1 otherwise. + """ + # Validate paths were specified using only one method + if len(parsed_args.paths) > 0 and parsed_args.files_from is not None: + logger.error("Paths cannot be specified both on the command line and through a file.") + return -1 + + # Validate extraction directory + extraction_dir = pathlib.Path(parsed_args.extraction_dir) + if not extraction_dir.is_dir(): + logger.error(f"extraction-dir ({extraction_dir}) is not a valid directory.") + return -1 + + # Validate and load config file + clp_config = validate_and_load_config_file( + clp_home, pathlib.Path(parsed_args.config), default_config_file_path + ) + if clp_config is None: + return -1 + + paths = parsed_args.paths + list_path = parsed_args.files_from + + logs_dir = clp_config.logs_directory + archives_dir = clp_config.archive_output.directory + # Generate database config file for clp db_config_file_path = logs_dir / f".decompress-db-config-{uuid.uuid4()}.yml" with open(db_config_file_path, "w") as f: yaml.safe_dump(clp_config.database.get_clp_connection_params_and_type(True), f) # fmt: off - decompression_cmd = [ + extract_cmd = [ str(clp_home / "bin" / "clp"), "x", str(archives_dir), str(extraction_dir), "--db-config-file", str(db_config_file_path), ] # fmt: on - files_to_decompress_list_path = None + + files_to_extract_list_path = None if list_path is not None: - decompression_cmd.append("-f") - decompression_cmd.append(str(list_path)) + extract_cmd.append("-f") + extract_cmd.append(str(list_path)) elif len(paths) > 0: # Write paths to file - files_to_decompress_list_path = logs_dir / f"paths-to-decompress-{uuid.uuid4()}.txt" - with open(files_to_decompress_list_path, "w") as stream: + files_to_extract_list_path = logs_dir / f"paths-to-extract-{uuid.uuid4()}.txt" + with open(files_to_extract_list_path, "w") as stream: for path in paths: stream.write(path + "\n") - decompression_cmd.append("-f") - decompression_cmd.append(str(files_to_decompress_list_path)) + extract_cmd.append("-f") + extract_cmd.append(str(files_to_extract_list_path)) - proc = subprocess.Popen(decompression_cmd) + proc = subprocess.Popen(extract_cmd) return_code = proc.wait() if 0 != return_code: - logger.error(f"Decompression failed, return_code={return_code}") + logger.error(f"File extraction failed, return_code={return_code}") return return_code # Remove generated files - if files_to_decompress_list_path is not None: - files_to_decompress_list_path.unlink() + if files_to_extract_list_path is not None: + files_to_extract_list_path.unlink() db_config_file_path.unlink() return 0 @@ -86,44 +253,41 @@ def main(argv): default=str(default_config_file_path), help="CLP configuration file.", ) - args_parser.add_argument("paths", metavar="PATH", nargs="*", help="Paths to decompress.") - args_parser.add_argument("-f", "--files-from", help="Decompress all paths in the given list.") - args_parser.add_argument( - "-d", "--extraction-dir", metavar="DIR", help="Decompress files into DIR", default="." + command_args_parser = args_parser.add_subparsers(dest="command", required=True) + + # File extraction command parser + file_extraction_parser = command_args_parser.add_parser(EXTRACT_FILE_CMD) + file_extraction_parser.add_argument( + "paths", metavar="PATH", nargs="*", help="Files to extract." + ) + file_extraction_parser.add_argument( + "-f", "--files-from", help="A file listing all files to extract." + ) + file_extraction_parser.add_argument( + "-d", "--extraction-dir", metavar="DIR", default=".", help="Extract files into DIR." ) - parsed_args = args_parser.parse_args(argv[1:]) - # Validate paths were specified using only one method - if len(parsed_args.paths) > 0 and parsed_args.files_from is not None: - args_parser.error("Paths cannot be specified both on the command line and through a file.") + # IR extraction command parser + ir_extraction_parser = command_args_parser.add_parser(EXTRACT_IR_CMD) + ir_extraction_parser.add_argument("msg_ix", type=int, help="Message index.") + ir_extraction_parser.add_argument( + "--target-uncompressed-size", type=int, help="Target uncompressed IR size." + ) - # Validate extraction directory - extraction_dir = pathlib.Path(parsed_args.extraction_dir) - if not extraction_dir.is_dir(): - logger.error(f"extraction-dir ({extraction_dir}) is not a valid directory.") - return -1 + group = ir_extraction_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--orig-file-id", type=str, help="Original file's ID.") + group.add_argument("--orig-file-path", type=str, help="Original file's path.") - # Validate and load config file - try: - config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) - clp_config.validate_archive_output_dir() - clp_config.validate_logs_dir() - except: - logger.exception("Failed to load config.") - return -1 + parsed_args = args_parser.parse_args(argv[1:]) - return decompress_paths( - clp_home, - parsed_args.paths, - parsed_args.files_from, - clp_config, - clp_config.archive_output.directory, - clp_config.logs_directory, - extraction_dir, - ) + command = parsed_args.command + if EXTRACT_FILE_CMD == command: + return handle_extract_file_cmd(parsed_args, clp_home, default_config_file_path) + elif EXTRACT_IR_CMD == command: + return handle_extract_ir_cmd(parsed_args, clp_home, default_config_file_path) + else: + logger.exception(f"Unexpected command: {command}") + return -1 if "__main__" == __name__: diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/search.py b/components/clp-package-utils/clp_package_utils/scripts/native/search.py index 384c39cc1..7dd247fa5 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/native/search.py +++ b/components/clp-package-utils/clp_package_utils/scripts/native/search.py @@ -4,24 +4,26 @@ import asyncio import ipaddress import logging -import multiprocessing import pathlib import socket import sys -import time -from contextlib import closing import msgpack import pymongo -from clp_py_utils.clp_config import Database, ResultsCache, SEARCH_JOBS_TABLE_NAME +from clp_py_utils.clp_config import Database, ResultsCache from clp_py_utils.sql_adapter import SQL_Adapter -from job_orchestration.scheduler.constants import SearchJobStatus -from job_orchestration.scheduler.job_config import AggregationConfig, SearchConfig +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.job_config import AggregationConfig, SearchJobConfig from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, get_clp_home, - validate_and_load_config_file, + load_config_file, +) +from clp_package_utils.scripts.native.utils import ( + run_function_in_process, + submit_query_job, + wait_for_query_job, ) # Setup logging @@ -35,41 +37,6 @@ logger.addHandler(logging_console_handler) -async def run_function_in_process(function, *args, initializer=None, init_args=None): - """ - Runs the given function in a separate process wrapped in a *cancellable* - asyncio task. This is necessary because asyncio's multiprocessing process - cannot be cancelled once it's started. - :param function: Method to run - :param args: Arguments for the method - :param initializer: Initializer for each process in the pool - :param init_args: Arguments for the initializer - :return: Return value of the method - """ - pool = multiprocessing.Pool(1, initializer, init_args) - - loop = asyncio.get_event_loop() - fut = loop.create_future() - - def process_done_callback(obj): - loop.call_soon_threadsafe(fut.set_result, obj) - - def process_error_callback(err): - loop.call_soon_threadsafe(fut.set_exception, err) - - pool.apply_async( - function, args, callback=process_done_callback, error_callback=process_error_callback - ) - - try: - return await fut - except asyncio.CancelledError: - pass - finally: - pool.terminate() - pool.close() - - def create_and_monitor_job_in_db( db_config: Database, results_cache: ResultsCache, @@ -83,7 +50,7 @@ def create_and_monitor_job_in_db( do_count_aggregation: bool | None, count_by_time_bucket_size: int | None, ): - search_config = SearchConfig( + search_config = SearchJobConfig( query_string=wildcard_query, begin_timestamp=begin_timestamp, end_timestamp=end_timestamp, @@ -106,47 +73,22 @@ def create_and_monitor_job_in_db( search_config.tags = tag_list sql_adapter = SQL_Adapter(db_config) - with closing(sql_adapter.create_connection(True)) as db_conn, closing( - db_conn.cursor(dictionary=True) - ) as db_cursor: - # Create job - db_cursor.execute( - f"INSERT INTO `{SEARCH_JOBS_TABLE_NAME}` (`search_config`) VALUES (%s)", - (msgpack.packb(search_config.dict()),), - ) - db_conn.commit() - job_id = db_cursor.lastrowid + job_id = submit_query_job(sql_adapter, search_config, QueryJobType.SEARCH_OR_AGGREGATION) + job_status = wait_for_query_job(sql_adapter, job_id) - # Wait for the job to be marked complete - while True: - db_cursor.execute( - f"SELECT `status` FROM `{SEARCH_JOBS_TABLE_NAME}` WHERE `id` = {job_id}" - ) - # There will only ever be one row since it's impossible to have more than one job with - # the same ID - new_status = db_cursor.fetchall()[0]["status"] - db_conn.commit() - if new_status in ( - SearchJobStatus.SUCCEEDED, - SearchJobStatus.FAILED, - SearchJobStatus.CANCELLED, - ): - break - - time.sleep(0.5) + if do_count_aggregation is None and count_by_time_bucket_size is None: + return + with pymongo.MongoClient(results_cache.get_uri()) as client: + search_results_collection = client[results_cache.db_name][str(job_id)] + if do_count_aggregation is not None: + for document in search_results_collection.find(): + print(f"tags: {document['group_tags']} count: {document['records'][0]['count']}") + elif count_by_time_bucket_size is not None: + for document in search_results_collection.find(): + print(f"timestamp: {document['timestamp']} count: {document['count']}") - if do_count_aggregation is None and count_by_time_bucket_size is None: - return - with pymongo.MongoClient(results_cache.get_uri()) as client: - search_results_collection = client[results_cache.db_name][str(job_id)] - if do_count_aggregation is not None: - for document in search_results_collection.find(): - print( - f"tags: {document['group_tags']} count: {document['records'][0]['count']}" - ) - elif count_by_time_bucket_size is not None: - for document in search_results_collection.find(): - print(f"timestamp: {document['timestamp']} count: {document['count']}") + if job_status != QueryJobStatus.SUCCEEDED: + logger.error(f"job {job_id} finished with unexpected status: {job_status}") async def worker_connection_handler(reader: asyncio.StreamReader, writer: asyncio.StreamWriter): @@ -160,9 +102,9 @@ async def worker_connection_handler(reader: asyncio.StreamReader, writer: asynci return unpacker.feed(buf) - # Print out any messages we can decode + # Print out any messages we can decode in the form of ORIG_PATH: MSG for unpacked in unpacker: - print(f"{unpacked[0]}: {unpacked[2]}", end="") + print(f"{unpacked[2]}: {unpacked[1]}", end="") except asyncio.CancelledError: return finally: @@ -328,9 +270,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() except: logger.exception("Failed to load config.") diff --git a/components/clp-package-utils/clp_package_utils/scripts/native/utils.py b/components/clp-package-utils/clp_package_utils/scripts/native/utils.py new file mode 100644 index 000000000..2f5c51a11 --- /dev/null +++ b/components/clp-package-utils/clp_package_utils/scripts/native/utils.py @@ -0,0 +1,98 @@ +import asyncio +import multiprocessing +import time +from contextlib import closing + +import msgpack +from clp_py_utils.clp_config import ( + QUERY_JOBS_TABLE_NAME, +) +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType +from job_orchestration.scheduler.scheduler_data import QueryJobConfig + + +async def run_function_in_process(function, *args, initializer=None, init_args=None): + """ + Runs the given function in a separate process wrapped in a *cancellable* + asyncio task. This is necessary because asyncio's multiprocessing process + cannot be cancelled once it's started. + :param function: Method to run + :param args: Arguments for the method + :param initializer: Initializer for each process in the pool + :param init_args: Arguments for the initializer + :return: Return value of the method + """ + pool = multiprocessing.Pool(1, initializer, init_args) + + loop = asyncio.get_event_loop() + fut = loop.create_future() + + def process_done_callback(obj): + loop.call_soon_threadsafe(fut.set_result, obj) + + def process_error_callback(err): + loop.call_soon_threadsafe(fut.set_exception, err) + + pool.apply_async( + function, args, callback=process_done_callback, error_callback=process_error_callback + ) + + try: + return await fut + except asyncio.CancelledError: + pass + finally: + pool.terminate() + pool.close() + + +def submit_query_job( + sql_adapter: SQL_Adapter, job_config: QueryJobConfig, job_type: QueryJobType +) -> int: + """ + Submits a query job. + :param sql_adapter: + :param job_config: + :param job_type: + :return: The job's ID. + """ + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + # Create job + db_cursor.execute( + f"INSERT INTO `{QUERY_JOBS_TABLE_NAME}` (`job_config`, `type`) VALUES (%s, %s)", + (msgpack.packb(job_config.dict()), job_type), + ) + db_conn.commit() + return db_cursor.lastrowid + + +def wait_for_query_job(sql_adapter: SQL_Adapter, job_id: int) -> QueryJobStatus: + """ + Waits for the query job with the given ID to complete. + :param sql_adapter: + :param job_id: + :return: The job's status on completion. + """ + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + # Wait for the job to be marked complete + while True: + db_cursor.execute( + f"SELECT `status` FROM `{QUERY_JOBS_TABLE_NAME}` WHERE `id` = {job_id}" + ) + # There will only ever be one row since it's impossible to have more than one job with + # the same ID + new_status = QueryJobStatus(db_cursor.fetchall()[0]["status"]) + db_conn.commit() + if new_status in ( + QueryJobStatus.SUCCEEDED, + QueryJobStatus.FAILED, + QueryJobStatus.CANCELLED, + ): + return new_status + + time.sleep(0.5) diff --git a/components/clp-package-utils/clp_package_utils/scripts/search.py b/components/clp-package-utils/clp_package_utils/scripts/search.py index 2f2450430..f3f02046d 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/search.py +++ b/components/clp-package-utils/clp_package_utils/scripts/search.py @@ -10,10 +10,13 @@ from clp_package_utils.general import ( CLP_DEFAULT_CONFIG_FILE_RELATIVE_PATH, - CONTAINER_CLP_HOME, + dump_container_config, generate_container_config, + generate_container_name, + generate_container_start_cmd, get_clp_home, - validate_and_load_config_file, + JobType, + load_config_file, validate_and_load_db_credentials_file, ) @@ -70,52 +73,32 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) clp_config.validate_logs_dir() # Validate and load necessary credentials - validate_and_load_db_credentials_file(clp_config, clp_home, True) + validate_and_load_db_credentials_file(clp_config, clp_home, False) except: logger.exception("Failed to load config.") return -1 - container_name = f"clp-search-{str(uuid.uuid4())[-4:]}" + container_name = generate_container_name(JobType.SEARCH) container_clp_config, mounts = generate_container_config(clp_config, clp_home) - container_config_filename = f".{container_name}-config.yml" - container_config_file_path_on_host = clp_config.logs_directory / container_config_filename - with open(container_config_file_path_on_host, "w") as f: - yaml.safe_dump(container_clp_config.dump_to_primitive_dict(), f) + generated_config_path_on_container, generated_config_path_on_host = dump_container_config( + container_clp_config, clp_config, container_name + ) - clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" - # fmt: off - container_start_cmd = [ - "docker", "run", - "-i", - "--rm", - "--network", "host", - "-w", str(CONTAINER_CLP_HOME), - "-e", f"PYTHONPATH={clp_site_packages_dir}", - "-u", f"{os.getuid()}:{os.getgid()}", - "--name", container_name, - "--log-driver", "local", - "--mount", str(mounts.clp_home), - ] - # fmt: on - necessary_mounts = [mounts.logs_dir] - for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) - container_start_cmd.append(clp_config.execution_container) + necessary_mounts = [mounts.clp_home, mounts.logs_dir] + container_start_cmd = generate_container_start_cmd( + container_name, necessary_mounts, clp_config.execution_container + ) # fmt: off search_cmd = [ "python3", "-m", "clp_package_utils.scripts.native.search", - "--config", str(container_clp_config.logs_directory / container_config_filename), + "--config", str(generated_config_path_on_container), parsed_args.wildcard_query, ] # fmt: on @@ -142,7 +125,7 @@ def main(argv): subprocess.run(cmd, check=True) # Remove generated files - container_config_file_path_on_host.unlink() + generated_config_path_on_host.unlink() return 0 diff --git a/components/clp-package-utils/clp_package_utils/scripts/start_clp.py b/components/clp-package-utils/clp_package_utils/scripts/start_clp.py index c90671f46..7c6de0200 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/start_clp.py +++ b/components/clp-package-utils/clp_package_utils/scripts/start_clp.py @@ -8,8 +8,8 @@ import subprocess import sys import time -import typing import uuid +from typing import Any, Dict, List, Optional import yaml from clp_py_utils.clp_config import ( @@ -21,13 +21,14 @@ COMPRESSION_WORKER_COMPONENT_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + QUERY_JOBS_TABLE_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, RESULTS_CACHE_COMPONENT_NAME, - SEARCH_JOBS_TABLE_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, ) from job_orchestration.scheduler.constants import QueueName @@ -44,11 +45,12 @@ get_clp_home, is_container_exited, is_container_running, - validate_and_load_config_file, + load_config_file, validate_and_load_db_credentials_file, validate_and_load_queue_credentials_file, validate_and_load_redis_credentials_file, validate_db_config, + validate_log_viewer_webui_config, validate_queue_config, validate_redis_config, validate_reducer_config, @@ -87,6 +89,21 @@ def append_docker_port_settings_for_host_ips( cmd.append(f"{ip}:{host_port}:{container_port}") +def chown_recursively( + path: pathlib.Path, + user_id: int, + group_id: int, +): + """ + Recursively changes the owner of the given path to the given user ID and group ID. + :param path: + :param user_id: + :param group_id: + """ + chown_cmd = ["chown", "--recursive", f"{user_id}:{group_id}", str(path)] + subprocess.run(chown_cmd, stdout=subprocess.DEVNULL, check=True) + + def wait_for_container_cmd(container_name: str, cmd_to_run: [str], timeout: int): container_exec_cmd = ["docker", "exec", container_name] cmd = container_exec_cmd + cmd_to_run @@ -232,6 +249,54 @@ def create_db_tables( logger.info(f"Created {component_name} tables.") +def create_results_cache_indices( + instance_id: str, + clp_config: CLPConfig, + container_clp_config: CLPConfig, + mounts: CLPDockerMounts, +): + component_name = RESULTS_CACHE_COMPONENT_NAME + logger.info(f"Creating {component_name} indices...") + + container_name = f"clp-{component_name}-indices-creator-{instance_id}" + + clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" + # fmt: off + container_start_cmd = [ + "docker", "run", + "-i", + "--network", "host", + "--rm", + "--name", container_name, + "--log-driver", "local", + "-e", f"PYTHONPATH={clp_site_packages_dir}", + "-u", f"{os.getuid()}:{os.getgid()}", + ] + # fmt: on + necessary_mounts = [mounts.clp_home, mounts.data_dir, mounts.logs_dir] + for mount in necessary_mounts: + if mount: + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) + container_start_cmd.append(clp_config.execution_container) + + clp_py_utils_dir = clp_site_packages_dir / "clp_py_utils" + # fmt: off + create_tables_cmd = [ + "python3", + str(clp_py_utils_dir / "create-results-cache-indices.py"), + "--uri", container_clp_config.results_cache.get_uri(), + "--ir-collection", container_clp_config.results_cache.ir_collection_name, + ] + # fmt: on + + cmd = container_start_cmd + create_tables_cmd + logger.debug(" ".join(cmd)) + subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + + logger.info(f"Created {component_name} indices.") + + def start_queue(instance_id: str, clp_config: CLPConfig): component_name = QUEUE_COMPONENT_NAME logger.info(f"Starting {component_name}...") @@ -268,6 +333,19 @@ def start_queue(instance_id: str, clp_config: CLPConfig): DockerMount(DockerMountType.BIND, queue_logs_dir, rabbitmq_logs_dir), ] rabbitmq_pid_file_path = pathlib.Path("/") / "tmp" / "rabbitmq.pid" + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "rabbitmq" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(queue_logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", @@ -277,7 +355,7 @@ def start_queue(instance_id: str, clp_config: CLPConfig): # Override RABBITMQ_LOGS since the image sets it to *only* log to stdout "-e", f"RABBITMQ_LOGS={rabbitmq_logs_dir / log_filename}", "-e", f"RABBITMQ_PID_FILE={rabbitmq_pid_file_path}", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user ] # fmt: on append_docker_port_settings_for_host_ips( @@ -330,13 +408,27 @@ def start_redis(instance_id: str, clp_config: CLPConfig, conf_dir: pathlib.Path) ), DockerMount(DockerMountType.BIND, redis_data_dir, pathlib.Path("/") / "data"), ] + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "redis" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(redis_data_dir, default_container_user_id, default_container_group_id) + chown_recursively(redis_logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", "-d", "--name", container_name, "--log-driver", "local", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user, ] # fmt: on for mount in mounts: @@ -350,6 +442,19 @@ def start_redis(instance_id: str, clp_config: CLPConfig, conf_dir: pathlib.Path) cmd.append(str(config_file_path)) subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + # fmt: off + redis_ping_cmd = [ + "redis-cli", + "-h", "127.0.0.1", + "-p", "6379", + "-a", clp_config.redis.password, + "PING" + ] + # fmt: on + + if not wait_for_container_cmd(container_name, redis_ping_cmd, 30): + raise EnvironmentError(f"{component_name} did not initialize in time") + logger.info(f"Started {component_name}.") @@ -376,13 +481,27 @@ def start_results_cache(instance_id: str, clp_config: CLPConfig, conf_dir: pathl DockerMount(DockerMountType.BIND, data_dir, pathlib.Path("/") / "data" / "db"), DockerMount(DockerMountType.BIND, logs_dir, pathlib.Path("/") / "var" / "log" / "mongodb"), ] + + host_user_id = os.getuid() + if 0 != host_user_id: + container_user = f"{host_user_id}:{os.getgid()}" + else: + # The host user is `root` so use the container's default user and make this component's + # directories writable by that user. + # NOTE: This doesn't affect the host user's access to the directories since they're `root`. + container_user = "mongodb" + default_container_user_id = 999 + default_container_group_id = 999 + chown_recursively(data_dir, default_container_user_id, default_container_group_id) + chown_recursively(logs_dir, default_container_user_id, default_container_group_id) + # fmt: off cmd = [ "docker", "run", "-d", "--name", container_name, "--log-driver", "local", - "-u", f"{os.getuid()}:{os.getgid()}", + "-u", container_user, ] # fmt: on for mount in mounts: @@ -416,15 +535,15 @@ def start_compression_scheduler( ) -def start_search_scheduler( +def start_query_scheduler( instance_id: str, clp_config: CLPConfig, container_clp_config: CLPConfig, mounts: CLPDockerMounts, ): - module_name = "job_orchestration.scheduler.search.search_scheduler" + module_name = "job_orchestration.scheduler.query.query_scheduler" generic_start_scheduler( - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, module_name, instance_id, clp_config, @@ -474,10 +593,10 @@ def generic_start_scheduler( "-e", ( f"RESULT_BACKEND=redis://default:{container_clp_config.redis.password}@" f"{container_clp_config.redis.host}:{container_clp_config.redis.port}/" - f"{container_clp_config.redis.search_backend_database}" + f"{container_clp_config.redis.query_backend_database}" ), "-e", f"CLP_LOGS_DIR={container_logs_dir}", - "-e", f"CLP_LOGGING_LEVEL={clp_config.search_scheduler.logging_level}", + "-e", f"CLP_LOGGING_LEVEL={clp_config.query_scheduler.logging_level}", "-u", f"{os.getuid()}:{os.getgid()}", "--mount", str(mounts.clp_home), ] @@ -526,29 +645,40 @@ def start_compression_worker( clp_config.redis.compression_backend_database, num_cpus, mounts, + None, + None, ) -def start_search_worker( +def start_query_worker( instance_id: str, clp_config: CLPConfig, container_clp_config: CLPConfig, num_cpus: int, mounts: CLPDockerMounts, ): - celery_method = "job_orchestration.executor.search" - celery_route = f"{QueueName.SEARCH}" + celery_method = "job_orchestration.executor.query" + celery_route = f"{QueueName.QUERY}" + + query_worker_mount = [mounts.ir_output_dir] + query_worker_env = { + "CLP_IR_OUTPUT_DIR": container_clp_config.ir_output.directory, + "CLP_IR_COLLECTION": clp_config.results_cache.ir_collection_name, + } + generic_start_worker( - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, instance_id, clp_config, - clp_config.search_worker, + clp_config.query_worker, container_clp_config, celery_method, celery_route, - clp_config.redis.search_backend_database, + clp_config.redis.query_backend_database, num_cpus, mounts, + query_worker_env, + query_worker_mount, ) @@ -563,6 +693,8 @@ def generic_start_worker( redis_database: int, num_cpus: int, mounts: CLPDockerMounts, + worker_specific_env: Dict[str, Any], + worker_specific_mount: List[Optional[DockerMount]], ): logger.info(f"Starting {component_name}...") @@ -578,6 +710,7 @@ def generic_start_worker( # Create necessary directories clp_config.archive_output.directory.mkdir(parents=True, exist_ok=True) + clp_config.ir_output.directory.mkdir(parents=True, exist_ok=True) clp_site_packages_dir = CONTAINER_CLP_HOME / "lib" / "python3" / "site-packages" # fmt: off @@ -605,19 +738,28 @@ def generic_start_worker( "-e", f"CLP_LOGGING_LEVEL={worker_config.logging_level}", "-e", f"CLP_STORAGE_ENGINE={clp_config.package.storage_engine}", "-u", f"{os.getuid()}:{os.getgid()}", - "--mount", str(mounts.clp_home), ] + if worker_specific_env: + for env_name, env_value in worker_specific_env.items(): + container_start_cmd.append("-e") + container_start_cmd.append(f"{env_name}={env_value}") + # fmt: on necessary_mounts = [ + mounts.clp_home, mounts.data_dir, mounts.logs_dir, mounts.archives_output_dir, mounts.input_logs_dir, ] + if worker_specific_mount: + necessary_mounts.extend(worker_specific_mount) + for mount in necessary_mounts: - if mount: - container_start_cmd.append("--mount") - container_start_cmd.append(str(mount)) + if not mount: + raise ValueError(f"Required mount configuration is empty: {necessary_mounts}") + container_start_cmd.append("--mount") + container_start_cmd.append(str(mount)) container_start_cmd.append(clp_config.execution_container) worker_cmd = [ @@ -643,13 +785,13 @@ def generic_start_worker( logger.info(f"Started {component_name}.") -def update_meteor_settings( +def update_settings_object( parent_key_prefix: str, - settings: typing.Dict[str, typing.Any], - updates: typing.Dict[str, typing.Any], + settings: Dict[str, Any], + updates: Dict[str, Any], ): """ - Recursively updates the given Meteor settings object with the values from `updates`. + Recursively updates the given settings object with the values from `updates`. :param parent_key_prefix: The prefix for keys at this level in the settings dictionary. :param settings: The settings to update. @@ -661,11 +803,25 @@ def update_meteor_settings( error_msg = f"{parent_key_prefix}{key} is not a valid configuration key for the webui." raise ValueError(error_msg) if isinstance(value, dict): - update_meteor_settings(f"{parent_key_prefix}{key}.", settings[key], value) + update_settings_object(f"{parent_key_prefix}{key}.", settings[key], value) else: settings[key] = updates[key] +def read_and_update_settings_json(settings_file_path: pathlib.Path, updates: Dict[str, Any]): + """ + Reads and updates a settings JSON file. + + :param settings_file_path: + :param updates: + """ + with open(settings_file_path, "r") as settings_json_file: + settings_object = json.loads(settings_json_file.read()) + update_settings_object("", settings_object, updates) + + return settings_object + + def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts): component_name = WEBUI_COMPONENT_NAME logger.info(f"Starting {component_name}...") @@ -675,10 +831,9 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts return webui_logs_dir = clp_config.logs_directory / component_name - node_path = str( - CONTAINER_CLP_HOME / "var" / "www" / "programs" / "server" / "npm" / "node_modules" - ) - settings_json_path = get_clp_home() / "var" / "www" / "settings.json" + container_webui_dir = CONTAINER_CLP_HOME / "var" / "www" / "webui" + node_path = str(container_webui_dir / "programs" / "server" / "npm" / "node_modules") + settings_json_path = get_clp_home() / "var" / "www" / "webui" / "settings.json" validate_webui_config(clp_config, webui_logs_dir, settings_json_path) @@ -686,8 +841,8 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts webui_logs_dir.mkdir(exist_ok=True, parents=True) container_webui_logs_dir = pathlib.Path("/") / "var" / "log" / component_name - with open(settings_json_path, "r") as settings_json_file: - meteor_settings = json.loads(settings_json_file.read()) + + # Read and update settings.json meteor_settings_updates = { "private": { "SqlDbHost": clp_config.database.host, @@ -696,13 +851,16 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts "SqlDbClpArchivesTableName": f"{CLP_METADATA_TABLE_PREFIX}archives", "SqlDbClpFilesTableName": f"{CLP_METADATA_TABLE_PREFIX}files", "SqlDbCompressionJobsTableName": COMPRESSION_JOBS_TABLE_NAME, - "SqlDbSearchJobsTableName": SEARCH_JOBS_TABLE_NAME, + "SqlDbQueryJobsTableName": QUERY_JOBS_TABLE_NAME, }, "public": { "ClpStorageEngine": clp_config.package.storage_engine, + "LogViewerWebuiUrl": ( + f"http://{clp_config.log_viewer_webui.host}:{clp_config.log_viewer_webui.port}", + ), }, } - update_meteor_settings("", meteor_settings, meteor_settings_updates) + meteor_settings = read_and_update_settings_json(settings_json_path, meteor_settings_updates) # Start container # fmt: off @@ -735,9 +893,85 @@ def start_webui(instance_id: str, clp_config: CLPConfig, mounts: CLPDockerMounts container_cmd.append(clp_config.execution_container) node_cmd = [ - str(CONTAINER_CLP_HOME / "bin" / "node"), - str(CONTAINER_CLP_HOME / "var" / "www" / "launcher.js"), - str(CONTAINER_CLP_HOME / "var" / "www" / "main.js"), + str(CONTAINER_CLP_HOME / "bin" / "node-14"), + str(container_webui_dir / "launcher.js"), + str(container_webui_dir / "main.js"), + ] + cmd = container_cmd + node_cmd + subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) + + logger.info(f"Started {component_name}.") + + +def start_log_viewer_webui( + instance_id: str, + clp_config: CLPConfig, + container_clp_config: CLPConfig, + mounts: CLPDockerMounts, +): + component_name = LOG_VIEWER_WEBUI_COMPONENT_NAME + logger.info(f"Starting {component_name}...") + + container_name = f"clp-{component_name}-{instance_id}" + if container_exists(container_name): + return + + container_log_viewer_webui_dir = CONTAINER_CLP_HOME / "var" / "www" / "log_viewer_webui" + node_path = str(container_log_viewer_webui_dir / "server" / "node_modules") + settings_json_path = ( + get_clp_home() / "var" / "www" / "log_viewer_webui" / "server" / "settings.json" + ) + + validate_log_viewer_webui_config(clp_config, settings_json_path) + + # Read, update, and write back settings.json + settings_json_updates = { + "SqlDbHost": clp_config.database.host, + "SqlDbPort": clp_config.database.port, + "SqlDbName": clp_config.database.name, + "SqlDbQueryJobsTableName": QUERY_JOBS_TABLE_NAME, + "MongoDbHost": clp_config.results_cache.host, + "MongoDbPort": clp_config.results_cache.port, + "MongoDbName": clp_config.results_cache.db_name, + "MongoDbIrFilesCollectionName": clp_config.results_cache.ir_collection_name, + "ClientDir": str(container_log_viewer_webui_dir / "client"), + "IrFilesDir": str(container_clp_config.ir_output.directory), + "LogViewerDir": str(container_log_viewer_webui_dir / "yscope-log-viewer"), + } + settings_json = read_and_update_settings_json(settings_json_path, settings_json_updates) + with open(settings_json_path, "w") as settings_json_file: + settings_json_file.write(json.dumps(settings_json)) + + # Start container + # fmt: off + container_cmd = [ + "docker", "run", + "-d", + "--network", "host", + "--name", container_name, + "--log-driver", "local", + "-e", f"NODE_PATH={node_path}", + "-e", f"HOST={clp_config.log_viewer_webui.host}", + "-e", f"PORT={clp_config.log_viewer_webui.port}", + "-e", f"CLP_DB_USER={clp_config.database.username}", + "-e", f"CLP_DB_PASS={clp_config.database.password}", + "-e", f"NODE_ENV=production", + "-u", f"{os.getuid()}:{os.getgid()}", + ] + # fmt: on + necessary_mounts = [ + mounts.clp_home, + mounts.ir_output_dir, + ] + for mount in necessary_mounts: + if mount: + container_cmd.append("--mount") + container_cmd.append(str(mount)) + container_cmd.append(clp_config.execution_container) + + node_cmd = [ + str(CONTAINER_CLP_HOME / "bin" / "node-22"), + str(container_log_viewer_webui_dir / "server" / "src" / "main.js"), ] cmd = container_cmd + node_cmd subprocess.run(cmd, stdout=subprocess.DEVNULL, check=True) @@ -839,14 +1073,15 @@ def main(argv): component_args_parser.add_parser(REDIS_COMPONENT_NAME) component_args_parser.add_parser(RESULTS_CACHE_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_SCHEDULER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_SCHEDULER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_SCHEDULER_COMPONENT_NAME) compression_worker_parser = component_args_parser.add_parser(COMPRESSION_WORKER_COMPONENT_NAME) add_num_workers_argument(compression_worker_parser) - search_worker_parser = component_args_parser.add_parser(SEARCH_WORKER_COMPONENT_NAME) - add_num_workers_argument(search_worker_parser) + query_worker_parser = component_args_parser.add_parser(QUERY_WORKER_COMPONENT_NAME) + add_num_workers_argument(query_worker_parser) reducer_server_parser = component_args_parser.add_parser(REDUCER_COMPONENT_NAME) add_num_workers_argument(reducer_server_parser) component_args_parser.add_parser(WEBUI_COMPONENT_NAME) + component_args_parser.add_parser(LOG_VIEWER_WEBUI_COMPONENT_NAME) parsed_args = args_parser.parse_args(argv[1:]) @@ -864,9 +1099,7 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) # Validate and load necessary credentials if target in ( @@ -874,8 +1107,9 @@ def main(argv): CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, ): validate_and_load_db_credentials_file(clp_config, clp_home, True) if target in ( @@ -883,9 +1117,9 @@ def main(argv): CONTROLLER_TARGET_NAME, QUEUE_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_queue_credentials_file(clp_config, clp_home, True) if target in ( @@ -893,9 +1127,9 @@ def main(argv): CONTROLLER_TARGET_NAME, REDIS_COMPONENT_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_redis_credentials_file(clp_config, clp_home, True) @@ -908,7 +1142,7 @@ def main(argv): if target in ( COMPRESSION_WORKER_COMPONENT_NAME, REDUCER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): num_workers = parsed_args.num_workers else: @@ -945,24 +1179,28 @@ def main(argv): start_redis(instance_id, clp_config, conf_dir) if target in (ALL_TARGET_NAME, RESULTS_CACHE_COMPONENT_NAME): start_results_cache(instance_id, clp_config, conf_dir) + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, RESULTS_CACHE_COMPONENT_NAME): + create_results_cache_indices(instance_id, clp_config, container_clp_config, mounts) if target in ( ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, COMPRESSION_SCHEDULER_COMPONENT_NAME, ): start_compression_scheduler(instance_id, clp_config, container_clp_config, mounts) - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, SEARCH_SCHEDULER_COMPONENT_NAME): - start_search_scheduler(instance_id, clp_config, container_clp_config, mounts) + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, QUERY_SCHEDULER_COMPONENT_NAME): + start_query_scheduler(instance_id, clp_config, container_clp_config, mounts) if target in (ALL_TARGET_NAME, COMPRESSION_WORKER_COMPONENT_NAME): start_compression_worker( instance_id, clp_config, container_clp_config, num_workers, mounts ) - if target in (ALL_TARGET_NAME, SEARCH_WORKER_COMPONENT_NAME): - start_search_worker(instance_id, clp_config, container_clp_config, num_workers, mounts) + if target in (ALL_TARGET_NAME, QUERY_WORKER_COMPONENT_NAME): + start_query_worker(instance_id, clp_config, container_clp_config, num_workers, mounts) if target in (ALL_TARGET_NAME, REDUCER_COMPONENT_NAME): start_reducer(instance_id, clp_config, container_clp_config, num_workers, mounts) if target in (ALL_TARGET_NAME, WEBUI_COMPONENT_NAME): start_webui(instance_id, clp_config, mounts) + if target in (ALL_TARGET_NAME, LOG_VIEWER_WEBUI_COMPONENT_NAME): + start_log_viewer_webui(instance_id, clp_config, container_clp_config, mounts) except Exception as ex: if type(ex) == ValueError: diff --git a/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py b/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py index 307e54236..f100a098a 100755 --- a/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py +++ b/components/clp-package-utils/clp_package_utils/scripts/stop_clp.py @@ -11,12 +11,13 @@ COMPRESSION_WORKER_COMPONENT_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, REDIS_COMPONENT_NAME, REDUCER_COMPONENT_NAME, RESULTS_CACHE_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, WEBUI_COMPONENT_NAME, ) @@ -25,7 +26,7 @@ get_clp_home, is_container_exited, is_container_running, - validate_and_load_config_file, + load_config_file, validate_and_load_db_credentials_file, validate_and_load_queue_credentials_file, ) @@ -88,10 +89,11 @@ def main(argv): component_args_parser.add_parser(REDUCER_COMPONENT_NAME) component_args_parser.add_parser(RESULTS_CACHE_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_SCHEDULER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_SCHEDULER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_SCHEDULER_COMPONENT_NAME) component_args_parser.add_parser(COMPRESSION_WORKER_COMPONENT_NAME) - component_args_parser.add_parser(SEARCH_WORKER_COMPONENT_NAME) + component_args_parser.add_parser(QUERY_WORKER_COMPONENT_NAME) component_args_parser.add_parser(WEBUI_COMPONENT_NAME) + component_args_parser.add_parser(LOG_VIEWER_WEBUI_COMPONENT_NAME) parsed_args = args_parser.parse_args(argv[1:]) @@ -103,12 +105,15 @@ def main(argv): # Validate and load config file try: config_file_path = pathlib.Path(parsed_args.config) - clp_config = validate_and_load_config_file( - config_file_path, default_config_file_path, clp_home - ) + clp_config = load_config_file(config_file_path, default_config_file_path, clp_home) # Validate and load necessary credentials - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, DB_COMPONENT_NAME): + if target in ( + ALL_TARGET_NAME, + CONTROLLER_TARGET_NAME, + DB_COMPONENT_NAME, + LOG_VIEWER_WEBUI_COMPONENT_NAME, + ): validate_and_load_db_credentials_file(clp_config, clp_home, False) if target in ( ALL_TARGET_NAME, @@ -116,8 +121,8 @@ def main(argv): COMPRESSION_SCHEDULER_COMPONENT_NAME, COMPRESSION_WORKER_COMPONENT_NAME, QUEUE_COMPONENT_NAME, - SEARCH_SCHEDULER_COMPONENT_NAME, - SEARCH_WORKER_COMPONENT_NAME, + QUERY_SCHEDULER_COMPONENT_NAME, + QUERY_WORKER_COMPONENT_NAME, ): validate_and_load_queue_credentials_file(clp_config, clp_home, False) except: @@ -136,6 +141,9 @@ def main(argv): already_exited_containers = [] force = parsed_args.force + if target in (ALL_TARGET_NAME, LOG_VIEWER_WEBUI_COMPONENT_NAME): + container_name = f"clp-{LOG_VIEWER_WEBUI_COMPONENT_NAME}-{instance_id}" + stop_running_container(container_name, already_exited_containers, force) if target in (ALL_TARGET_NAME, WEBUI_COMPONENT_NAME): container_name = f"clp-{WEBUI_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) @@ -146,14 +154,14 @@ def main(argv): container_config_file_path = logs_dir / f"{container_name}.yml" if container_config_file_path.exists(): container_config_file_path.unlink() - if target in (ALL_TARGET_NAME, SEARCH_WORKER_COMPONENT_NAME): - container_name = f"clp-{SEARCH_WORKER_COMPONENT_NAME}-{instance_id}" + if target in (ALL_TARGET_NAME, QUERY_WORKER_COMPONENT_NAME): + container_name = f"clp-{QUERY_WORKER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) if target in (ALL_TARGET_NAME, COMPRESSION_WORKER_COMPONENT_NAME): container_name = f"clp-{COMPRESSION_WORKER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) - if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, SEARCH_SCHEDULER_COMPONENT_NAME): - container_name = f"clp-{SEARCH_SCHEDULER_COMPONENT_NAME}-{instance_id}" + if target in (ALL_TARGET_NAME, CONTROLLER_TARGET_NAME, QUERY_SCHEDULER_COMPONENT_NAME): + container_name = f"clp-{QUERY_SCHEDULER_COMPONENT_NAME}-{instance_id}" stop_running_container(container_name, already_exited_containers, force) container_config_file_path = logs_dir / f"{container_name}.yml" diff --git a/components/clp-py-utils/clp_py_utils/clp_config.py b/components/clp-py-utils/clp_py_utils/clp_config.py index 142ce614e..f5a813057 100644 --- a/components/clp-py-utils/clp_py_utils/clp_config.py +++ b/components/clp-py-utils/clp_py_utils/clp_config.py @@ -22,16 +22,18 @@ REDUCER_COMPONENT_NAME = "reducer" RESULTS_CACHE_COMPONENT_NAME = "results_cache" COMPRESSION_SCHEDULER_COMPONENT_NAME = "compression_scheduler" -SEARCH_SCHEDULER_COMPONENT_NAME = "search_scheduler" +QUERY_SCHEDULER_COMPONENT_NAME = "query_scheduler" COMPRESSION_WORKER_COMPONENT_NAME = "compression_worker" -SEARCH_WORKER_COMPONENT_NAME = "search_worker" +QUERY_WORKER_COMPONENT_NAME = "query_worker" WEBUI_COMPONENT_NAME = "webui" +LOG_VIEWER_WEBUI_COMPONENT_NAME = "log_viewer_webui" # Target names ALL_TARGET_NAME = "" CONTROLLER_TARGET_NAME = "controller" -SEARCH_JOBS_TABLE_NAME = "search_jobs" +QUERY_JOBS_TABLE_NAME = "query_jobs" +QUERY_TASKS_TABLE_NAME = "query_tasks" COMPRESSION_JOBS_TABLE_NAME = "compression_jobs" COMPRESSION_TASKS_TABLE_NAME = "compression_tasks" @@ -152,6 +154,20 @@ def _validate_logging_level(cls, field): ) +def _validate_host(cls, field): + if "" == field: + raise ValueError(f"{cls.__name__}.host cannot be empty.") + + +def _validate_port(cls, field): + min_valid_port = 0 + max_valid_port = 2**16 - 1 + if min_valid_port > field or max_valid_port < field: + raise ValueError( + f"{cls.__name__}.port is not within valid range " f"{min_valid_port}-{max_valid_port}." + ) + + class CompressionScheduler(BaseModel): jobs_poll_delay: float = 0.1 # seconds logging_level: str = "INFO" @@ -162,7 +178,7 @@ def validate_logging_level(cls, field): return field -class SearchScheduler(BaseModel): +class QueryScheduler(BaseModel): host = "localhost" port = 7000 jobs_poll_delay: float = 0.1 # seconds @@ -196,7 +212,7 @@ def validate_logging_level(cls, field): return field -class SearchWorker(BaseModel): +class QueryWorker(BaseModel): logging_level: str = "INFO" @validator("logging_level") @@ -208,7 +224,7 @@ def validate_logging_level(cls, field): class Redis(BaseModel): host: str = "localhost" port: int = 6379 - search_backend_database: int = 0 + query_backend_database: int = 0 compression_backend_database: int = 1 # redis can perform authentication without a username password: typing.Optional[str] @@ -253,7 +269,8 @@ def validate_upsert_interval(cls, field): class ResultsCache(BaseModel): host: str = "localhost" port: int = 27017 - db_name: str = "clp-search" + db_name: str = "clp-query-results" + ir_collection_name: str = "ir-files" @validator("host") def validate_host(cls, field): @@ -267,6 +284,12 @@ def validate_db_name(cls, field): raise ValueError(f"{RESULTS_CACHE_COMPONENT_NAME}.db_name cannot be empty.") return field + @validator("ir_collection_name") + def validate_ir_collection_name(cls, field): + if "" == field: + raise ValueError(f"{RESULTS_CACHE_COMPONENT_NAME}.ir_collection_name cannot be empty.") + return field + def get_uri(self): return f"mongodb://{self.host}:{self.port}/{self.db_name}" @@ -320,6 +343,32 @@ def dump_to_primitive_dict(self): return d +class IrOutput(BaseModel): + directory: pathlib.Path = pathlib.Path("var") / "data" / "ir" + target_uncompressed_size: int = 128 * 1024 * 1024 + + @validator("directory") + def validate_directory(cls, field): + if "" == field: + raise ValueError("directory can not be empty") + return field + + @validator("target_uncompressed_size") + def validate_target_uncompressed_size(cls, field): + if field <= 0: + raise ValueError("target_uncompressed_size must be greater than 0") + return field + + def make_config_paths_absolute(self, clp_home: pathlib.Path): + self.directory = make_config_path_absolute(clp_home, self.directory) + + def dump_to_primitive_dict(self): + d = self.dict() + # Turn directory (pathlib.Path) into a primitive string + d["directory"] = str(d["directory"]) + return d + + class WebUi(BaseModel): host: str = "localhost" port: int = 4000 @@ -327,19 +376,12 @@ class WebUi(BaseModel): @validator("host") def validate_host(cls, field): - if "" == field: - raise ValueError(f"{WEBUI_COMPONENT_NAME}.host cannot be empty.") + _validate_host(cls, field) return field @validator("port") def validate_port(cls, field): - min_valid_port = 0 - max_valid_port = 2**16 - 1 - if min_valid_port > field or max_valid_port < field: - raise ValueError( - f"{WEBUI_COMPONENT_NAME}.port is not within valid range " - f"{min_valid_port}-{max_valid_port}." - ) + _validate_port(cls, field) return field @validator("logging_level") @@ -348,6 +390,21 @@ def validate_logging_level(cls, field): return field +class LogViewerWebUi(BaseModel): + host: str = "localhost" + port: int = 3000 + + @validator("host") + def validate_host(cls, field): + _validate_host(cls, field) + return field + + @validator("port") + def validate_port(cls, field): + _validate_port(cls, field) + return field + + class CLPConfig(BaseModel): execution_container: typing.Optional[str] @@ -360,13 +417,15 @@ class CLPConfig(BaseModel): reducer: Reducer() = Reducer() results_cache: ResultsCache = ResultsCache() compression_scheduler: CompressionScheduler = CompressionScheduler() - search_scheduler: SearchScheduler = SearchScheduler() + query_scheduler: QueryScheduler = QueryScheduler() compression_worker: CompressionWorker = CompressionWorker() - search_worker: SearchWorker = SearchWorker() + query_worker: QueryWorker = QueryWorker() webui: WebUi = WebUi() + log_viewer_webui: LogViewerWebUi = LogViewerWebUi() credentials_file_path: pathlib.Path = CLP_DEFAULT_CREDENTIALS_FILE_PATH archive_output: ArchiveOutput = ArchiveOutput() + ir_output: IrOutput = IrOutput() data_directory: pathlib.Path = pathlib.Path("var") / "data" logs_directory: pathlib.Path = pathlib.Path("var") / "log" @@ -376,6 +435,7 @@ def make_config_paths_absolute(self, clp_home: pathlib.Path): self.input_logs_directory = make_config_path_absolute(clp_home, self.input_logs_directory) self.credentials_file_path = make_config_path_absolute(clp_home, self.credentials_file_path) self.archive_output.make_config_paths_absolute(clp_home) + self.ir_output.make_config_paths_absolute(clp_home) self.data_directory = make_config_path_absolute(clp_home, self.data_directory) self.logs_directory = make_config_path_absolute(clp_home, self.logs_directory) self._os_release_file_path = make_config_path_absolute(clp_home, self._os_release_file_path) @@ -395,6 +455,12 @@ def validate_archive_output_dir(self): except ValueError as ex: raise ValueError(f"archive_output.directory is invalid: {ex}") + def validate_ir_output_dir(self): + try: + validate_path_could_be_dir(self.ir_output.directory) + except ValueError as ex: + raise ValueError(f"ir_output.directory is invalid: {ex}") + def validate_data_dir(self): try: validate_path_could_be_dir(self.data_directory) @@ -462,6 +528,7 @@ def load_redis_credentials_from_file(self): def dump_to_primitive_dict(self): d = self.dict() d["archive_output"] = self.archive_output.dump_to_primitive_dict() + d["ir_output"] = self.ir_output.dump_to_primitive_dict() # Turn paths into primitive strings d["input_logs_directory"] = str(self.input_logs_directory) d["credentials_file_path"] = str(self.credentials_file_path) diff --git a/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py b/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py new file mode 100644 index 000000000..dafbd3bde --- /dev/null +++ b/components/clp-py-utils/clp_py_utils/create-results-cache-indices.py @@ -0,0 +1,42 @@ +import argparse +import logging +import sys + +from pymongo import IndexModel, MongoClient + +# Setup logging +# Create logger +logger = logging.getLogger(__file__) +logger.setLevel(logging.INFO) +# Setup console logging +logging_console_handler = logging.StreamHandler() +logging_formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s") +logging_console_handler.setFormatter(logging_formatter) +logger.addHandler(logging_console_handler) + + +def main(argv): + args_parser = argparse.ArgumentParser(description="Creates results cache indices for CLP.") + args_parser.add_argument("--uri", required=True, help="URI of the results cache.") + args_parser.add_argument("--ir-collection", required=True, help="Collection for IR metadata.") + parsed_args = args_parser.parse_args(argv[1:]) + + results_cache_uri = parsed_args.uri + ir_collection_name = parsed_args.ir_collection + + try: + with MongoClient(results_cache_uri) as results_cache_client: + ir_collection = results_cache_client.get_default_database()[ir_collection_name] + + file_split_id_index = IndexModel(["file_split_id"]) + orig_file_id_index = IndexModel(["orig_file_id", "begin_msg_ix", "end_msg_ix"]) + ir_collection.create_indexes([file_split_id_index, orig_file_id_index]) + except Exception: + logger.exception("Failed to create clp results cache indices.") + return -1 + + return 0 + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py b/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py index 5fca4745f..1ed727367 100644 --- a/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py +++ b/components/clp-py-utils/clp_py_utils/initialize-orchestration-db.py @@ -7,7 +7,8 @@ from job_orchestration.scheduler.constants import ( CompressionJobStatus, CompressionTaskStatus, - SearchJobStatus, + QueryJobStatus, + QueryTaskStatus, ) from sql_adapter import SQL_Adapter @@ -15,7 +16,8 @@ COMPRESSION_JOBS_TABLE_NAME, COMPRESSION_TASKS_TABLE_NAME, Database, - SEARCH_JOBS_TABLE_NAME, + QUERY_JOBS_TABLE_NAME, + QUERY_TASKS_TABLE_NAME, ) from clp_py_utils.core import read_yaml_config_file @@ -84,25 +86,52 @@ def main(argv): INDEX `job_id` (`job_id`) USING BTREE, INDEX `TASK_STATUS` (`status`) USING BTREE, INDEX `TASK_START_TIME` (`start_time`) USING BTREE, - CONSTRAINT `compression_tasks` FOREIGN KEY (`job_id`) - REFERENCES `compression_jobs` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION + CONSTRAINT `{COMPRESSION_TASKS_TABLE_NAME}` FOREIGN KEY (`job_id`) + REFERENCES `{COMPRESSION_JOBS_TABLE_NAME}` (`id`) + ON UPDATE NO ACTION ON DELETE NO ACTION ) ROW_FORMAT=DYNAMIC """ ) scheduling_db_cursor.execute( f""" - CREATE TABLE IF NOT EXISTS `{SEARCH_JOBS_TABLE_NAME}` ( + CREATE TABLE IF NOT EXISTS `{QUERY_JOBS_TABLE_NAME}` ( `id` INT NOT NULL AUTO_INCREMENT, - `status` INT NOT NULL DEFAULT '{SearchJobStatus.PENDING}', - `submission_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), - `search_config` VARBINARY(60000) NOT NULL, + `type` INT NOT NULL, + `status` INT NOT NULL DEFAULT '{QueryJobStatus.PENDING}', + `creation_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `num_tasks` INT NOT NULL DEFAULT '0', + `num_tasks_completed` INT NOT NULL DEFAULT '0', + `start_time` DATETIME(3) NULL DEFAULT NULL, + `duration` FLOAT NULL DEFAULT NULL, + `job_config` VARBINARY(60000) NOT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `JOB_STATUS` (`status`) USING BTREE ) ROW_FORMAT=DYNAMIC """ ) + scheduling_db_cursor.execute( + f""" + CREATE TABLE IF NOT EXISTS `{QUERY_TASKS_TABLE_NAME}` ( + `id` BIGINT NOT NULL AUTO_INCREMENT, + `status` INT NOT NULL DEFAULT '{QueryTaskStatus.PENDING}', + `creation_time` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + `start_time` DATETIME(3) NULL DEFAULT NULL, + `duration` FLOAT NULL DEFAULT NULL, + `job_id` INT NOT NULL, + `archive_id` VARCHAR(255) NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `job_id` (`job_id`) USING BTREE, + INDEX `TASK_STATUS` (`status`) USING BTREE, + INDEX `TASK_START_TIME` (`start_time`) USING BTREE, + CONSTRAINT `{QUERY_TASKS_TABLE_NAME}` FOREIGN KEY (`job_id`) + REFERENCES `{QUERY_JOBS_TABLE_NAME}` (`id`) + ON UPDATE NO ACTION ON DELETE NO ACTION + ) ROW_FORMAT=DYNAMIC + """ + ) + scheduling_db.commit() except: logger.exception("Failed to create scheduling tables.") diff --git a/components/core/.clang-format b/components/core/.clang-format index 18195cc31..99a0d74ce 100644 --- a/components/core/.clang-format +++ b/components/core/.clang-format @@ -75,7 +75,7 @@ IncludeCategories: # Library headers. Update when adding new libraries. # NOTE: clang-format retains leading white-space on a line in violation of the YAML spec. - Regex: "<(absl|antlr4|archive|boost|bsoncxx|catch2|curl|date|fmt|json|log_surgeon|mariadb\ -|mongocxx|msgpack|simdjson|spdlog|sqlite3|string_utils|yaml-cpp|zstd)" +|mongocxx|msgpack|openssl|outcome|regex_utils|simdjson|spdlog|sqlite3|string_utils|yaml-cpp|zstd)" Priority: 3 # C system headers - Regex: "^<.+\\.h>" diff --git a/components/core/CMakeLists.txt b/components/core/CMakeLists.txt index 63dc1ab08..70090ba30 100644 --- a/components/core/CMakeLists.txt +++ b/components/core/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.5.1) +cmake_minimum_required(VERSION 3.16.3) project(CLP LANGUAGES CXX C) if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) @@ -146,6 +146,14 @@ else() message(FATAL_ERROR "Could not find ${CLP_LIBS_STRING} libraries for CURL") endif() +# Find OpenSSL +find_package(OpenSSL REQUIRED) +if (OPENSSL_FOUND) + message(STATUS "Found OpenSSL (${OPENSSL_VERSION})") +else () + message(FATAL_ERROR "OpenSSL not found") +endif () + # Add log surgeon add_subdirectory(submodules/log-surgeon EXCLUDE_FROM_ALL) @@ -209,6 +217,7 @@ set(sqlite_DYNAMIC_LIBS "dl;m;pthread") include(cmake/Modules/FindLibraryDependencies.cmake) FindDynamicLibraryDependencies(sqlite "${sqlite_DYNAMIC_LIBS}") +add_subdirectory(src/clp/regex_utils) add_subdirectory(src/clp/string_utils) add_subdirectory(src/clp/clg) @@ -282,6 +291,8 @@ set(SOURCE_FILES_unitTest src/clp/CurlDownloadHandler.cpp src/clp/CurlDownloadHandler.hpp src/clp/CurlEasyHandle.hpp + src/clp/CurlGlobalInstance.cpp + src/clp/CurlGlobalInstance.hpp src/clp/CurlOperationFailed.hpp src/clp/CurlStringList.hpp src/clp/database_utils.cpp @@ -305,6 +316,10 @@ set(SOURCE_FILES_unitTest src/clp/ffi/ir_stream/encoding_methods.cpp src/clp/ffi/ir_stream/encoding_methods.hpp src/clp/ffi/ir_stream/protocol_constants.hpp + src/clp/ffi/ir_stream/Serializer.cpp + src/clp/ffi/ir_stream/Serializer.hpp + src/clp/ffi/ir_stream/utils.cpp + src/clp/ffi/ir_stream/utils.hpp src/clp/ffi/SchemaTree.cpp src/clp/ffi/SchemaTree.hpp src/clp/ffi/SchemaTreeNode.hpp @@ -322,6 +337,10 @@ set(SOURCE_FILES_unitTest src/clp/ffi/search/Subquery.hpp src/clp/ffi/search/WildcardToken.cpp src/clp/ffi/search/WildcardToken.hpp + src/clp/ffi/utils.cpp + src/clp/ffi/utils.hpp + src/clp/FileDescriptor.cpp + src/clp/FileDescriptor.hpp src/clp/FileReader.cpp src/clp/FileReader.hpp src/clp/FileWriter.cpp @@ -335,9 +354,16 @@ set(SOURCE_FILES_unitTest src/clp/GlobalSQLiteMetadataDB.hpp src/clp/Grep.cpp src/clp/Grep.hpp + src/clp/hash_utils.cpp + src/clp/hash_utils.hpp + src/clp/ir/constants.hpp + src/clp/ir/EncodedTextAst.cpp + src/clp/ir/EncodedTextAst.hpp src/clp/ir/LogEvent.hpp src/clp/ir/LogEventDeserializer.cpp src/clp/ir/LogEventDeserializer.hpp + src/clp/ir/LogEventSerializer.cpp + src/clp/ir/LogEventSerializer.hpp src/clp/ir/parsing.cpp src/clp/ir/parsing.hpp src/clp/ir/parsing.inc @@ -376,6 +402,8 @@ set(SOURCE_FILES_unitTest src/clp/Query.hpp src/clp/ReaderInterface.cpp src/clp/ReaderInterface.hpp + src/clp/ReadOnlyMemoryMappedFile.cpp + src/clp/ReadOnlyMemoryMappedFile.hpp src/clp/spdlog_with_specializations.hpp src/clp/SQLiteDB.cpp src/clp/SQLiteDB.hpp @@ -422,11 +450,13 @@ set(SOURCE_FILES_unitTest src/clp/StringReader.hpp src/clp/Thread.cpp src/clp/Thread.hpp + src/clp/time_types.hpp src/clp/TimestampPattern.cpp src/clp/TimestampPattern.hpp src/clp/TraceableException.hpp - src/clp/time_types.hpp src/clp/type_utils.hpp + src/clp/utf8_utils.cpp + src/clp/utf8_utils.hpp src/clp/Utils.cpp src/clp/Utils.hpp src/clp/VariableDictionaryEntry.cpp @@ -446,20 +476,25 @@ set(SOURCE_FILES_unitTest tests/test-encoding_methods.cpp tests/test-ffi_SchemaTree.cpp tests/test-Grep.cpp + tests/test-hash_utils.cpp tests/test-ir_encoding_methods.cpp tests/test-ir_parsing.cpp + tests/test-ir_serializer.cpp tests/test-kql.cpp tests/test-main.cpp tests/test-math_utils.cpp + tests/test-MemoryMappedFile.cpp tests/test-NetworkReader.cpp tests/test-ParserWithUserSchema.cpp tests/test-query_methods.cpp + tests/test-regex_utils.cpp tests/test-Segment.cpp tests/test-SQLiteDB.cpp tests/test-Stopwatch.cpp tests/test-StreamingCompression.cpp tests/test-string_utils.cpp tests/test-TimestampPattern.cpp + tests/test-utf8_utils.cpp tests/test-Utils.cpp ) add_executable(unitTest ${SOURCE_FILES_unitTest} ${SOURCE_FILES_clp_s_unitTest}) @@ -478,8 +513,10 @@ target_link_libraries(unitTest LibArchive::LibArchive MariaDBClient::MariaDBClient spdlog::spdlog + OpenSSL::Crypto ${sqlite_LIBRARY_DEPENDENCIES} ${STD_FS_LIBS} + clp::regex_utils clp::string_utils yaml-cpp::yaml-cpp ZStd::ZStd diff --git a/components/core/cmake/Modules/FindOpenSSL.cmake b/components/core/cmake/Modules/FindOpenSSL.cmake deleted file mode 100644 index 79c3b044b..000000000 --- a/components/core/cmake/Modules/FindOpenSSL.cmake +++ /dev/null @@ -1,534 +0,0 @@ -# NOTE: This is FindOpenSSL.cmake from cmake-3.16.0-rc3. It fixes issues with missing dependencies for the OpenSSL static library. -# This file should be deleted when cmake-3.16 is released. - -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying -# file Copyright.txt or https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -FindOpenSSL ------------ - -Find the OpenSSL encryption library. - -Optional COMPONENTS -^^^^^^^^^^^^^^^^^^^ - -This module supports two optional COMPONENTS: ``Crypto`` and ``SSL``. Both -components have associated imported targets, as described below. - -Imported Targets -^^^^^^^^^^^^^^^^ - -This module defines the following :prop_tgt:`IMPORTED` targets: - -``OpenSSL::SSL`` - The OpenSSL ``ssl`` library, if found. -``OpenSSL::Crypto`` - The OpenSSL ``crypto`` library, if found. - -Result Variables -^^^^^^^^^^^^^^^^ - -This module will set the following variables in your project: - -``OPENSSL_FOUND`` - System has the OpenSSL library. If no components are requested it only - requires the crypto library. -``OPENSSL_INCLUDE_DIR`` - The OpenSSL include directory. -``OPENSSL_CRYPTO_LIBRARY`` - The OpenSSL crypto library. -``OPENSSL_CRYPTO_LIBRARIES`` - The OpenSSL crypto library and its dependencies. -``OPENSSL_SSL_LIBRARY`` - The OpenSSL SSL library. -``OPENSSL_SSL_LIBRARIES`` - The OpenSSL SSL library and its dependencies. -``OPENSSL_LIBRARIES`` - All OpenSSL libraries and their dependencies. -``OPENSSL_VERSION`` - This is set to ``$major.$minor.$revision$patch`` (e.g. ``0.9.8s``). - -Hints -^^^^^ - -Set ``OPENSSL_ROOT_DIR`` to the root directory of an OpenSSL installation. -Set ``OPENSSL_USE_STATIC_LIBS`` to ``TRUE`` to look for static libraries. -Set ``OPENSSL_MSVC_STATIC_RT`` set ``TRUE`` to choose the MT version of the lib. -#]=======================================================================] - -macro(_OpenSSL_test_and_find_dependencies ssl_library crypto_library) - if((CMAKE_SYSTEM_NAME STREQUAL "Linux") AND - (("${ssl_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") OR - ("${crypto_library}" MATCHES "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$"))) - set(_OpenSSL_has_dependencies TRUE) - find_package(Threads) - else() - set(_OpenSSL_has_dependencies FALSE) - endif() -endmacro() - -function(_OpenSSL_add_dependencies libraries_var library) - if(CMAKE_THREAD_LIBS_INIT) - list(APPEND ${libraries_var} ${CMAKE_THREAD_LIBS_INIT}) - endif() - list(APPEND ${libraries_var} ${CMAKE_DL_LIBS}) - set(${libraries_var} ${${libraries_var}} PARENT_SCOPE) -endfunction() - -function(_OpenSSL_target_add_dependencies target) - if(_OpenSSL_has_dependencies) - set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES Threads::Threads ) - set_property( TARGET ${target} APPEND PROPERTY INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS} ) - endif() -endfunction() - -if (UNIX) - find_package(PkgConfig QUIET) - pkg_check_modules(_OPENSSL QUIET openssl) -endif () - -# Support preference of static libs by adjusting CMAKE_FIND_LIBRARY_SUFFIXES -if(OPENSSL_USE_STATIC_LIBS) - set(_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - if(WIN32) - set(CMAKE_FIND_LIBRARY_SUFFIXES .lib .a ${CMAKE_FIND_LIBRARY_SUFFIXES}) - else() - set(CMAKE_FIND_LIBRARY_SUFFIXES .a ) - endif() -endif() - -if (WIN32) - # http://www.slproweb.com/products/Win32OpenSSL.html - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1;Inno Setup: App Path]" - "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1;Inno Setup: App Path]" - ENV OPENSSL_ROOT_DIR - ) - file(TO_CMAKE_PATH "$ENV{PROGRAMFILES}" _programfiles) - set(_OPENSSL_ROOT_PATHS - "${_programfiles}/OpenSSL" - "${_programfiles}/OpenSSL-Win32" - "${_programfiles}/OpenSSL-Win64" - "C:/OpenSSL/" - "C:/OpenSSL-Win32/" - "C:/OpenSSL-Win64/" - ) - unset(_programfiles) -else () - set(_OPENSSL_ROOT_HINTS - ${OPENSSL_ROOT_DIR} - ENV OPENSSL_ROOT_DIR - ) -endif () - -set(_OPENSSL_ROOT_HINTS_AND_PATHS - HINTS ${_OPENSSL_ROOT_HINTS} - PATHS ${_OPENSSL_ROOT_PATHS} - ) - -find_path(OPENSSL_INCLUDE_DIR - NAMES - openssl/ssl.h - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_INCLUDEDIR} - PATH_SUFFIXES - include -) - -if(WIN32 AND NOT CYGWIN) - if(MSVC) - # /MD and /MDd are the standard values - if someone wants to use - # others, the libnames have to change here too - # use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b - # enable OPENSSL_MSVC_STATIC_RT to get the libs build /MT (Multithreaded no-DLL) - # In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix: - # * MD for dynamic-release - # * MDd for dynamic-debug - # * MT for static-release - # * MTd for static-debug - - # Implementation details: - # We are using the libraries located in the VC subdir instead of the parent directory even though : - # libeay32MD.lib is identical to ../libeay32.lib, and - # ssleay32MD.lib is identical to ../ssleay32.lib - # enable OPENSSL_USE_STATIC_LIBS to use the static libs located in lib/VC/static - - if (OPENSSL_MSVC_STATIC_RT) - set(_OPENSSL_MSVC_RT_MODE "MT") - else () - set(_OPENSSL_MSVC_RT_MODE "MD") - endif () - - # Since OpenSSL 1.1, lib names are like libcrypto32MTd.lib and libssl32MTd.lib - if( "${CMAKE_SIZEOF_VOID_P}" STREQUAL "8" ) - set(_OPENSSL_MSVC_ARCH_SUFFIX "64") - else() - set(_OPENSSL_MSVC_ARCH_SUFFIX "32") - endif() - - if(OPENSSL_USE_STATIC_LIBS) - set(_OPENSSL_PATH_SUFFIXES - "lib/VC/static" - "VC/static" - "lib" - ) - else() - set(_OPENSSL_PATH_SUFFIXES - "lib/VC" - "VC" - "lib" - ) - endif () - - find_library(LIB_EAY_DEBUG - NAMES - libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d - libcrypto${_OPENSSL_MSVC_RT_MODE}d - libcryptod - libeay32${_OPENSSL_MSVC_RT_MODE}d - libeay32d - cryptod - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(LIB_EAY_RELEASE - NAMES - libcrypto${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE} - libcrypto${_OPENSSL_MSVC_RT_MODE} - libcrypto - libeay32${_OPENSSL_MSVC_RT_MODE} - libeay32 - crypto - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(SSL_EAY_DEBUG - NAMES - libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE}d - libssl${_OPENSSL_MSVC_RT_MODE}d - libssld - ssleay32${_OPENSSL_MSVC_RT_MODE}d - ssleay32d - ssld - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - find_library(SSL_EAY_RELEASE - NAMES - libssl${_OPENSSL_MSVC_ARCH_SUFFIX}${_OPENSSL_MSVC_RT_MODE} - libssl${_OPENSSL_MSVC_RT_MODE} - libssl - ssleay32${_OPENSSL_MSVC_RT_MODE} - ssleay32 - ssl - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - ${_OPENSSL_PATH_SUFFIXES} - ) - - set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}") - set(LIB_EAY_LIBRARY_RELEASE "${LIB_EAY_RELEASE}") - set(SSL_EAY_LIBRARY_DEBUG "${SSL_EAY_DEBUG}") - set(SSL_EAY_LIBRARY_RELEASE "${SSL_EAY_RELEASE}") - - include(SelectLibraryConfigurations) - select_library_configurations(LIB_EAY) - select_library_configurations(SSL_EAY) - - mark_as_advanced(LIB_EAY_LIBRARY_DEBUG LIB_EAY_LIBRARY_RELEASE - SSL_EAY_LIBRARY_DEBUG SSL_EAY_LIBRARY_RELEASE) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY_LIBRARY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY_LIBRARY} ) - elseif(MINGW) - # same player, for MinGW - set(LIB_EAY_NAMES crypto libeay32) - set(SSL_EAY_NAMES ssl ssleay32) - find_library(LIB_EAY - NAMES - ${LIB_EAY_NAMES} - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib/MinGW" - "lib" - ) - - find_library(SSL_EAY - NAMES - ${SSL_EAY_NAMES} - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - PATH_SUFFIXES - "lib/MinGW" - "lib" - ) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - unset(LIB_EAY_NAMES) - unset(SSL_EAY_NAMES) - else() - # Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues: - find_library(LIB_EAY - NAMES - libcrypto - libeay32 - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - find_library(SSL_EAY - NAMES - libssl - ssleay32 - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - mark_as_advanced(SSL_EAY LIB_EAY) - set(OPENSSL_SSL_LIBRARY ${SSL_EAY} ) - set(OPENSSL_CRYPTO_LIBRARY ${LIB_EAY} ) - endif() -else() - - find_library(OPENSSL_SSL_LIBRARY - NAMES - ssl - ssleay32 - ssleay32MD - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - find_library(OPENSSL_CRYPTO_LIBRARY - NAMES - crypto - NAMES_PER_DIR - ${_OPENSSL_ROOT_HINTS_AND_PATHS} - HINTS - ${_OPENSSL_LIBDIR} - PATH_SUFFIXES - lib - ) - - mark_as_advanced(OPENSSL_CRYPTO_LIBRARY OPENSSL_SSL_LIBRARY) - -endif() - -# compat defines -set(OPENSSL_SSL_LIBRARIES ${OPENSSL_SSL_LIBRARY}) -set(OPENSSL_CRYPTO_LIBRARIES ${OPENSSL_CRYPTO_LIBRARY}) -_OpenSSL_test_and_find_dependencies("${OPENSSL_SSL_LIBRARY}" "${OPENSSL_CRYPTO_LIBRARY}") -if(_OpenSSL_has_dependencies) - _OpenSSL_add_dependencies( OPENSSL_SSL_LIBRARIES "${OPENSSL_SSL_LIBRARY}" ) - _OpenSSL_add_dependencies( OPENSSL_CRYPTO_LIBRARIES "${OPENSSL_CRYPTO_LIBRARY}" ) -endif() - -function(from_hex HEX DEC) - string(TOUPPER "${HEX}" HEX) - set(_res 0) - string(LENGTH "${HEX}" _strlen) - - while (_strlen GREATER 0) - math(EXPR _res "${_res} * 16") - string(SUBSTRING "${HEX}" 0 1 NIBBLE) - string(SUBSTRING "${HEX}" 1 -1 HEX) - if (NIBBLE STREQUAL "A") - math(EXPR _res "${_res} + 10") - elseif (NIBBLE STREQUAL "B") - math(EXPR _res "${_res} + 11") - elseif (NIBBLE STREQUAL "C") - math(EXPR _res "${_res} + 12") - elseif (NIBBLE STREQUAL "D") - math(EXPR _res "${_res} + 13") - elseif (NIBBLE STREQUAL "E") - math(EXPR _res "${_res} + 14") - elseif (NIBBLE STREQUAL "F") - math(EXPR _res "${_res} + 15") - else() - math(EXPR _res "${_res} + ${NIBBLE}") - endif() - - string(LENGTH "${HEX}" _strlen) - endwhile() - - set(${DEC} ${_res} PARENT_SCOPE) -endfunction() - -if(OPENSSL_INCLUDE_DIR AND EXISTS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h") - file(STRINGS "${OPENSSL_INCLUDE_DIR}/openssl/opensslv.h" openssl_version_str - REGEX "^#[\t ]*define[\t ]+OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])+.*") - - if(openssl_version_str) - # The version number is encoded as 0xMNNFFPPS: major minor fix patch status - # The status gives if this is a developer or prerelease and is ignored here. - # Major, minor, and fix directly translate into the version numbers shown in - # the string. The patch field translates to the single character suffix that - # indicates the bug fix state, which 00 -> nothing, 01 -> a, 02 -> b and so - # on. - - string(REGEX REPLACE "^.*OPENSSL_VERSION_NUMBER[\t ]+0x([0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F]).*$" - "\\1;\\2;\\3;\\4;\\5" OPENSSL_VERSION_LIST "${openssl_version_str}") - list(GET OPENSSL_VERSION_LIST 0 OPENSSL_VERSION_MAJOR) - list(GET OPENSSL_VERSION_LIST 1 OPENSSL_VERSION_MINOR) - from_hex("${OPENSSL_VERSION_MINOR}" OPENSSL_VERSION_MINOR) - list(GET OPENSSL_VERSION_LIST 2 OPENSSL_VERSION_FIX) - from_hex("${OPENSSL_VERSION_FIX}" OPENSSL_VERSION_FIX) - list(GET OPENSSL_VERSION_LIST 3 OPENSSL_VERSION_PATCH) - - if (NOT OPENSSL_VERSION_PATCH STREQUAL "00") - from_hex("${OPENSSL_VERSION_PATCH}" _tmp) - # 96 is the ASCII code of 'a' minus 1 - math(EXPR OPENSSL_VERSION_PATCH_ASCII "${_tmp} + 96") - unset(_tmp) - # Once anyone knows how OpenSSL would call the patch versions beyond 'z' - # this should be updated to handle that, too. This has not happened yet - # so it is simply ignored here for now. - string(ASCII "${OPENSSL_VERSION_PATCH_ASCII}" OPENSSL_VERSION_PATCH_STRING) - endif () - - set(OPENSSL_VERSION "${OPENSSL_VERSION_MAJOR}.${OPENSSL_VERSION_MINOR}.${OPENSSL_VERSION_FIX}${OPENSSL_VERSION_PATCH_STRING}") - endif () -endif () - -set(OPENSSL_LIBRARIES ${OPENSSL_SSL_LIBRARIES} ${OPENSSL_CRYPTO_LIBRARIES} ) -list(REMOVE_DUPLICATES OPENSSL_LIBRARIES) - -foreach(_comp IN LISTS OpenSSL_FIND_COMPONENTS) - if(_comp STREQUAL "Crypto") - if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND - (EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR - EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR - EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - ) - set(OpenSSL_${_comp}_FOUND TRUE) - else() - set(OpenSSL_${_comp}_FOUND FALSE) - endif() - elseif(_comp STREQUAL "SSL") - if(EXISTS "${OPENSSL_INCLUDE_DIR}" AND - (EXISTS "${OPENSSL_SSL_LIBRARY}" OR - EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR - EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - ) - set(OpenSSL_${_comp}_FOUND TRUE) - else() - set(OpenSSL_${_comp}_FOUND FALSE) - endif() - else() - message(WARNING "${_comp} is not a valid OpenSSL component") - set(OpenSSL_${_comp}_FOUND FALSE) - endif() -endforeach() -unset(_comp) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OpenSSL - REQUIRED_VARS - OPENSSL_CRYPTO_LIBRARY - OPENSSL_INCLUDE_DIR - VERSION_VAR - OPENSSL_VERSION - HANDLE_COMPONENTS - FAIL_MESSAGE - "Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the system variable OPENSSL_ROOT_DIR" -) - -mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES) - -if(OPENSSL_FOUND) - if(NOT TARGET OpenSSL::Crypto AND - (EXISTS "${OPENSSL_CRYPTO_LIBRARY}" OR - EXISTS "${LIB_EAY_LIBRARY_DEBUG}" OR - EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - ) - add_library(OpenSSL::Crypto UNKNOWN IMPORTED) - set_target_properties(OpenSSL::Crypto PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}") - if(EXISTS "${OPENSSL_CRYPTO_LIBRARY}") - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${OPENSSL_CRYPTO_LIBRARY}") - endif() - if(EXISTS "${LIB_EAY_LIBRARY_RELEASE}") - set_property(TARGET OpenSSL::Crypto APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${LIB_EAY_LIBRARY_RELEASE}") - endif() - if(EXISTS "${LIB_EAY_LIBRARY_DEBUG}") - set_property(TARGET OpenSSL::Crypto APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(OpenSSL::Crypto PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${LIB_EAY_LIBRARY_DEBUG}") - endif() - _OpenSSL_target_add_dependencies(OpenSSL::Crypto) - endif() - - if(NOT TARGET OpenSSL::SSL AND - (EXISTS "${OPENSSL_SSL_LIBRARY}" OR - EXISTS "${SSL_EAY_LIBRARY_DEBUG}" OR - EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - ) - add_library(OpenSSL::SSL UNKNOWN IMPORTED) - set_target_properties(OpenSSL::SSL PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${OPENSSL_INCLUDE_DIR}") - if(EXISTS "${OPENSSL_SSL_LIBRARY}") - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES "C" - IMPORTED_LOCATION "${OPENSSL_SSL_LIBRARY}") - endif() - if(EXISTS "${SSL_EAY_LIBRARY_RELEASE}") - set_property(TARGET OpenSSL::SSL APPEND PROPERTY - IMPORTED_CONFIGURATIONS RELEASE) - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_RELEASE "C" - IMPORTED_LOCATION_RELEASE "${SSL_EAY_LIBRARY_RELEASE}") - endif() - if(EXISTS "${SSL_EAY_LIBRARY_DEBUG}") - set_property(TARGET OpenSSL::SSL APPEND PROPERTY - IMPORTED_CONFIGURATIONS DEBUG) - set_target_properties(OpenSSL::SSL PROPERTIES - IMPORTED_LINK_INTERFACE_LANGUAGES_DEBUG "C" - IMPORTED_LOCATION_DEBUG "${SSL_EAY_LIBRARY_DEBUG}") - endif() - if(TARGET OpenSSL::Crypto) - set_target_properties(OpenSSL::SSL PROPERTIES - INTERFACE_LINK_LIBRARIES OpenSSL::Crypto) - endif() - _OpenSSL_target_add_dependencies(OpenSSL::SSL) - endif() -endif() - -# Restore the original find library ordering -if(OPENSSL_USE_STATIC_LIBS) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${_openssl_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) -endif() diff --git a/components/core/src/clp/CurlGlobalInstance.cpp b/components/core/src/clp/CurlGlobalInstance.cpp new file mode 100644 index 000000000..7add2afe9 --- /dev/null +++ b/components/core/src/clp/CurlGlobalInstance.cpp @@ -0,0 +1,45 @@ +#include "CurlGlobalInstance.hpp" + +#include + +#include + +#include "CurlOperationFailed.hpp" +#include "ErrorCode.hpp" + +namespace clp { +CurlGlobalInstance::CurlGlobalInstance() { + std::scoped_lock const global_lock{m_ref_count_mutex}; + if (0 == m_ref_count) { + if (auto const err{curl_global_init(CURL_GLOBAL_ALL)}; 0 != err) { + throw CurlOperationFailed( + ErrorCode_Failure, + __FILE__, + __LINE__, + err, + "`curl_global_init` failed." + ); + } + } + ++m_ref_count; +} + +CurlGlobalInstance::~CurlGlobalInstance() { + std::scoped_lock const global_lock{m_ref_count_mutex}; + --m_ref_count; + if (0 == m_ref_count) { +#if defined(__APPLE__) + // NOTE: On macOS, calling `curl_global_init` after `curl_global_cleanup` will fail with + // CURLE_SSL_CONNECT_ERROR. Thus, for now, we skip `deinit` on macOS. Luckily, it is safe to + // call `curl_global_init` multiple times without calling `curl_global_cleanup`. Related + // issues: + // - https://github.com/curl/curl/issues/12525 + // - https://github.com/curl/curl/issues/13805 + // TODO: Remove this conditional logic when the issues are resolved. + return; +#else + curl_global_cleanup(); +#endif + } +} +} // namespace clp diff --git a/components/core/src/clp/CurlGlobalInstance.hpp b/components/core/src/clp/CurlGlobalInstance.hpp new file mode 100644 index 000000000..56ec60ed8 --- /dev/null +++ b/components/core/src/clp/CurlGlobalInstance.hpp @@ -0,0 +1,35 @@ +#ifndef CLP_CURLGLOBALINSTANCE_HPP +#define CLP_CURLGLOBALINSTANCE_HPP + +#include +#include + +namespace clp { +/** + * Class to wrap `libcurl`'s global initialization/de-initialization calls using RAII. Before using + * any `libcurl` functionalities, an instance of this class must be created. Although unnecessasry, + * it can be safely instantiated multiple times; it maintains a static reference count to all + * existing instances and only de-initializes `libcurl`'s global resources when the reference count + * reaches 0. + */ +class CurlGlobalInstance { +public: + // Constructors + CurlGlobalInstance(); + + // Disable copy/move constructors and assignment operators + CurlGlobalInstance(CurlGlobalInstance const&) = delete; + CurlGlobalInstance(CurlGlobalInstance&&) = delete; + auto operator=(CurlGlobalInstance const&) -> CurlGlobalInstance& = delete; + auto operator=(CurlGlobalInstance&&) -> CurlGlobalInstance& = delete; + + // Destructor + ~CurlGlobalInstance(); + +private: + static inline std::mutex m_ref_count_mutex; + static inline size_t m_ref_count{0}; +}; +} // namespace clp + +#endif // CLP_CURLGLOBALINSTANCE_HPP diff --git a/components/core/src/clp/EncodedVariableInterpreter.cpp b/components/core/src/clp/EncodedVariableInterpreter.cpp index ad7116bfe..8170f2ddc 100644 --- a/components/core/src/clp/EncodedVariableInterpreter.cpp +++ b/components/core/src/clp/EncodedVariableInterpreter.cpp @@ -234,7 +234,8 @@ void EncodedVariableInterpreter::encode_and_add_to_dictionary( size_t& raw_num_bytes ) { logtype_dict_entry.clear(); - logtype_dict_entry.reserve_constant_length(log_event.get_logtype().length()); + auto const& log_message = log_event.get_message(); + logtype_dict_entry.reserve_constant_length(log_message.get_logtype().length()); raw_num_bytes = 0; @@ -284,9 +285,9 @@ void EncodedVariableInterpreter::encode_and_add_to_dictionary( }; ffi::ir_stream::generic_decode_message( - log_event.get_logtype(), - log_event.get_encoded_vars(), - log_event.get_dict_vars(), + log_message.get_logtype(), + log_message.get_encoded_vars(), + log_message.get_dict_vars(), constant_handler, encoded_int_handler, encoded_float_handler, diff --git a/components/core/src/clp/FileDescriptor.cpp b/components/core/src/clp/FileDescriptor.cpp new file mode 100644 index 000000000..2e17bfe05 --- /dev/null +++ b/components/core/src/clp/FileDescriptor.cpp @@ -0,0 +1,63 @@ +#include "FileDescriptor.hpp" + +#include +#include +#include + +#include +#include +#include + +#include "ErrorCode.hpp" +#include "type_utils.hpp" + +namespace clp { +FileDescriptor::FileDescriptor( + std::string_view path, + OpenMode open_mode, + CloseFailureCallback close_failure_callback +) + : m_open_mode{open_mode}, + m_close_failure_callback{close_failure_callback} { + // For newly created files, we enable writing for the owner and reading for everyone. + // Callers can change the created file's permissions as necessary. + constexpr auto cNewFilePermission{S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH}; + auto const flag{enum_to_underlying_type(open_mode)}; + if (0 != (flag & O_CREAT)) { + m_fd = open(path.data(), flag, cNewFilePermission); + } else { + m_fd = open(path.data(), flag); + } + if (-1 == m_fd) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "Failed to open file descriptor for path: " + std::string{path} + ); + } +} + +FileDescriptor::~FileDescriptor() { + if (-1 == m_fd) { + return; + } + if (0 != close(m_fd) && nullptr != m_close_failure_callback) { + m_close_failure_callback(errno); + } +} + +auto FileDescriptor::get_size() const -> size_t { + struct stat stat_result {}; + + if (0 != fstat(m_fd, &stat_result)) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "Failed to stat file using file descriptor." + ); + } + return static_cast(stat_result.st_size); +} +} // namespace clp diff --git a/components/core/src/clp/FileDescriptor.hpp b/components/core/src/clp/FileDescriptor.hpp new file mode 100644 index 000000000..a704326bf --- /dev/null +++ b/components/core/src/clp/FileDescriptor.hpp @@ -0,0 +1,93 @@ +#ifndef CLP_FILEDESCRIPTOR_HPP +#define CLP_FILEDESCRIPTOR_HPP + +#include + +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +/** + * Wrapper for a UNIX file descriptor. + */ +class FileDescriptor { +public: + // Types + /** + * `close` is called in the destructor to close the file descriptor. However, `close` may return + * an error indicated by `errno`. This type alias defines a callback to handle the `close` + * failure in the destructor. + * The signature of the callback: void close_failure_callback(int errno) + */ + using CloseFailureCallback = void (*)(int); + + class OperationFailed : public TraceableException { + public: + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string msg + ) + : TraceableException{error_code, filename, line_number}, + m_msg{std::move(msg)} {} + + [[nodiscard]] auto what() const noexcept -> char const* override { return m_msg.c_str(); } + + private: + std::string m_msg; + }; + + /** + * A C++ wrapper for Unix oflag that describes the open mode. + */ + // NOLINTNEXTLINE(performance-enum-size) + enum class OpenMode : int { + ReadOnly = O_RDONLY, + CreateForWrite = O_WRONLY | O_CREAT | O_TRUNC, + }; + + // Constructors + FileDescriptor( + std::string_view path, + OpenMode open_mode, + CloseFailureCallback close_failure_callback = nullptr + ); + + // Destructor + ~FileDescriptor(); + + // Disable copy/move constructors/assignment operators + FileDescriptor(FileDescriptor const&) = delete; + FileDescriptor(FileDescriptor&&) = delete; + auto operator=(FileDescriptor const&) -> FileDescriptor& = delete; + auto operator=(FileDescriptor&&) -> FileDescriptor& = delete; + + /** + * @return The raw fd. + */ + [[nodiscard]] auto get_raw_fd() const -> int { return m_fd; } + + /** + * @return The size of the file. + */ + [[nodiscard]] auto get_size() const -> size_t; + + /** + * @return The open mode. + */ + [[nodiscard]] auto get_open_mode() const -> OpenMode { return m_open_mode; } + +private: + int m_fd{-1}; + OpenMode m_open_mode; + CloseFailureCallback m_close_failure_callback{nullptr}; +}; +} // namespace clp + +#endif diff --git a/components/core/src/clp/NetworkReader.cpp b/components/core/src/clp/NetworkReader.cpp index 1ab746a54..c0b5359bf 100644 --- a/components/core/src/clp/NetworkReader.cpp +++ b/components/core/src/clp/NetworkReader.cpp @@ -110,33 +110,6 @@ curl_write_callback(char* ptr, size_t size, size_t nmemb, void* reader_ptr) -> s } } // namespace -bool NetworkReader::m_static_init_complete{false}; - -auto NetworkReader::init() -> ErrorCode { - if (m_static_init_complete) { - return ErrorCode_Success; - } - if (0 != curl_global_init(CURL_GLOBAL_ALL)) { - return ErrorCode_Failure; - } - m_static_init_complete = true; - return ErrorCode_Success; -} - -auto NetworkReader::deinit() -> void { -#if defined(__APPLE__) - // NOTE: On macOS, calling `curl_global_init` after `curl_global_cleanup` will fail with - // CURLE_SSL_CONNECT_ERROR. Thus, for now, we skip `deinit` on macOS. Related issues: - // - https://github.com/curl/curl/issues/12525 - // - https://github.com/curl/curl/issues/13805 - // TODO: Remove this conditional logic when the issues are resolved. - return; -#else - curl_global_cleanup(); - m_static_init_complete = false; -#endif -} - NetworkReader::NetworkReader( std::string_view src_url, size_t offset, @@ -157,9 +130,6 @@ NetworkReader::NetworkReader( // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) m_buffer_pool.emplace_back(std::make_unique(m_buffer_size)); } - if (false == m_static_init_complete) { - throw OperationFailed(ErrorCode_NotReady, __FILE__, __LINE__); - } m_downloader_thread = std::make_unique(*this, offset, disable_caching); m_downloader_thread->start(); } diff --git a/components/core/src/clp/NetworkReader.hpp b/components/core/src/clp/NetworkReader.hpp index 5a593029f..68e6d1019 100644 --- a/components/core/src/clp/NetworkReader.hpp +++ b/components/core/src/clp/NetworkReader.hpp @@ -18,6 +18,7 @@ #include #include "CurlDownloadHandler.hpp" +#include "CurlGlobalInstance.hpp" #include "ErrorCode.hpp" #include "ReaderInterface.hpp" #include "Thread.hpp" @@ -69,24 +70,20 @@ class NetworkReader : public ReaderInterface { static constexpr size_t cMinBufferPoolSize{2}; static constexpr size_t cMinBufferSize{512}; - /** - * Initializes static resources for this class. This must be called before using the class. - * @return ErrorCode_Success on success. - * @return ErrorCode_Failure if libcurl initialization failed. - */ - [[nodiscard]] static auto init() -> ErrorCode; - - /** - * De-initializes any static resources. - */ - static auto deinit() -> void; - /** * Constructs a reader to stream data from the given URL, starting at the given offset. - * TODO: the current implementation doesn't handle the case when the given offset is out of - * range. The file_pos will be set to an invalid state if this happens, which can be - * problematic if the other part of the program depends on this position. It can be fixed by - * capturing the error code 416 in the response header. + * NOTE: This class depends on `libcurl`, so an instance of `clp::CurlGlobalInstance` must + * remain alive for the entire lifespan of any instance of this class. + * + * This class maintains an instance of `CurlGlobalInstance` in case the user forgets to + * instantiate one, but it is better for performance if the user instantiates one. For instance, + * if the user doesn't instantiate a `CurlGlobalInstance` and only ever creates one + * `NetworkReader` at a time, then every construction and destruction of `NetworkReader` would + * cause `libcurl` to init and deinit. In contrast, if the user instantiates a + * `CurlGlobalInstance` before instantiating any `NetworkReader`s, then the `CurlGlobalInstance` + * maintained by this class will simply be a reference to the existing one rather than + * initializing and deinitializing `libcurl`. + * * @param src_url * @param offset Index of the byte at which to start the download * @param disable_caching Whether to disable the caching. @@ -246,8 +243,6 @@ class NetworkReader : public ReaderInterface { bool m_disable_caching{false}; }; - static bool m_static_init_complete; - /** * Submits a request to abort the ongoing curl download session. */ @@ -308,6 +303,8 @@ class NetworkReader : public ReaderInterface { return m_at_least_one_byte_downloaded.load(); } + CurlGlobalInstance m_curl_global_instance; + std::string m_src_url; size_t m_offset{0}; diff --git a/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp b/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp new file mode 100644 index 000000000..167b675a5 --- /dev/null +++ b/components/core/src/clp/ReadOnlyMemoryMappedFile.cpp @@ -0,0 +1,41 @@ +#include "ReadOnlyMemoryMappedFile.hpp" + +#include + +#include +#include + +#include "ErrorCode.hpp" +#include "FileDescriptor.hpp" + +namespace clp { +ReadOnlyMemoryMappedFile::ReadOnlyMemoryMappedFile(std::string_view path) { + FileDescriptor const fd{path, FileDescriptor::OpenMode::ReadOnly}; + auto const file_size{fd.get_size()}; + if (0 == file_size) { + // `mmap` doesn't allow mapping an empty file, so we don't need to call it. + return; + } + auto* mmap_ptr{mmap(nullptr, file_size, PROT_READ, MAP_PRIVATE, fd.get_raw_fd(), 0)}; + if (MAP_FAILED == mmap_ptr) { + throw OperationFailed( + ErrorCode_errno, + __FILE__, + __LINE__, + "`mmap` failed to map path: " + std::string{path} + ); + } + m_data = mmap_ptr; + m_buf_size = file_size; +} + +ReadOnlyMemoryMappedFile::~ReadOnlyMemoryMappedFile() { + if (0 == m_buf_size) { + // We don't call `mmap` for empty files, so we don't need to call `munmap`. + return; + } + // We skip error checking since the only likely reason for `munmap` to fail is if we give it + // invalid arguments. + munmap(m_data, m_buf_size); +} +} // namespace clp diff --git a/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp b/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp new file mode 100644 index 000000000..5749d3bd2 --- /dev/null +++ b/components/core/src/clp/ReadOnlyMemoryMappedFile.hpp @@ -0,0 +1,66 @@ +#ifndef CLP_READONLYMEMORYMAPPEDFILE_HPP +#define CLP_READONLYMEMORYMAPPEDFILE_HPP + +#include +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +/** + * A class for mapping a read-only file into memory. It maintains the memory buffer created by the + * underlying `mmap` system call and provides methods to get a view of the memory buffer. + */ +class ReadOnlyMemoryMappedFile { +public: + // Types + class OperationFailed : public TraceableException { + public: + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string msg + ) + : TraceableException{error_code, filename, line_number}, + m_msg{std::move(msg)} {} + + [[nodiscard]] auto what() const noexcept -> char const* override { return m_msg.c_str(); } + + private: + std::string m_msg; + }; + + // Constructors + /** + * @param path The path of the file to map. + */ + explicit ReadOnlyMemoryMappedFile(std::string_view path); + + // Destructor + ~ReadOnlyMemoryMappedFile(); + + // Disable copy/move constructors/assignment operators + ReadOnlyMemoryMappedFile(ReadOnlyMemoryMappedFile const&) = delete; + ReadOnlyMemoryMappedFile(ReadOnlyMemoryMappedFile&&) = delete; + auto operator=(ReadOnlyMemoryMappedFile const&) -> ReadOnlyMemoryMappedFile& = delete; + auto operator=(ReadOnlyMemoryMappedFile&&) -> ReadOnlyMemoryMappedFile& = delete; + + /** + * @return A view of the mapped file in memory. + */ + [[nodiscard]] auto get_view() const -> std::span { + return std::span{static_cast(m_data), m_buf_size}; + } + +private: + void* m_data{nullptr}; + size_t m_buf_size{0}; +}; +} // namespace clp + +#endif diff --git a/components/core/src/clp/clg/CMakeLists.txt b/components/core/src/clp/clg/CMakeLists.txt index 37c5a3710..a0ca5e9d0 100644 --- a/components/core/src/clp/clg/CMakeLists.txt +++ b/components/core/src/clp/clg/CMakeLists.txt @@ -18,6 +18,8 @@ set( ../ffi/ir_stream/decoding_methods.cpp ../ffi/ir_stream/decoding_methods.hpp ../ffi/ir_stream/decoding_methods.inc + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -31,6 +33,8 @@ set( ../GlobalSQLiteMetadataDB.hpp ../Grep.cpp ../Grep.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp ../ir/parsing.cpp ../ir/parsing.hpp @@ -57,6 +61,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp diff --git a/components/core/src/clp/clo/CMakeLists.txt b/components/core/src/clp/clo/CMakeLists.txt index b798e918a..931bffeaf 100644 --- a/components/core/src/clp/clo/CMakeLists.txt +++ b/components/core/src/clp/clo/CMakeLists.txt @@ -4,6 +4,8 @@ set( ../BufferReader.hpp ../cli_utils.cpp ../cli_utils.hpp + ../clp/FileDecompressor.cpp + ../clp/FileDecompressor.hpp ../database_utils.cpp ../database_utils.hpp ../Defs.h @@ -20,13 +22,23 @@ set( ../ffi/ir_stream/decoding_methods.cpp ../ffi/ir_stream/decoding_methods.hpp ../ffi/ir_stream/decoding_methods.inc + ../ffi/ir_stream/encoding_methods.cpp + ../ffi/ir_stream/encoding_methods.hpp + ../ffi/ir_stream/utils.cpp + ../ffi/ir_stream/utils.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp ../FileWriter.hpp ../Grep.cpp ../Grep.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp + ../ir/LogEventSerializer.cpp + ../ir/LogEventSerializer.hpp ../ir/parsing.cpp ../ir/parsing.hpp ../ir/parsing.inc @@ -49,6 +61,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp @@ -111,6 +125,7 @@ set( clo.cpp CommandLineArguments.cpp CommandLineArguments.hpp + constants.hpp OutputHandler.cpp OutputHandler.hpp ) diff --git a/components/core/src/clp/clo/CommandLineArguments.cpp b/components/core/src/clp/clo/CommandLineArguments.cpp index 8f7d54a12..fffc3d783 100644 --- a/components/core/src/clp/clo/CommandLineArguments.cpp +++ b/components/core/src/clp/clo/CommandLineArguments.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -18,6 +19,7 @@ using std::endl; using std::exception; using std::invalid_argument; using std::string; +using std::string_view; using std::vector; namespace clp::clo { @@ -56,6 +58,248 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { ); // clang-format on + po::options_description general_positional_options; + char command_input{}; + general_positional_options.add_options()("command", po::value(&command_input))( + "command-args", + po::value>() + ); + po::positional_options_description general_positional_options_description; + general_positional_options_description.add("command", 1); + general_positional_options_description.add("command-args", -1); + + // Aggregate all options + po::options_description all_options; + all_options.add(options_general); + all_options.add(general_positional_options); + + // Parse options + try { + // Parse options specified on the command line + po::parsed_options parsed = po::command_line_parser(argc, argv) + .options(all_options) + .positional(general_positional_options_description) + .allow_unregistered() + .run(); + po::variables_map parsed_command_line_options; + store(parsed, parsed_command_line_options); + + // Handle config-file manually since Boost won't set it until we call notify, and we can't + // call notify until we parse the config file + if (0 != parsed_command_line_options.count("config-file")) { + config_file_path = parsed_command_line_options["config-file"].as(); + } + + // Parse options specified through the config file + // NOTE: Command line arguments will take priority over config file since they are parsed + // first and Boost doesn't replace existing options + std::ifstream config_file(config_file_path); + if (config_file.is_open()) { + // Allow unrecognized options in configuration file since some of them may be + // exclusively for clp or other applications + po::parsed_options parsed_config_file + = po::parse_config_file(config_file, all_options, true); + store(parsed_config_file, parsed_command_line_options); + config_file.close(); + } + + notify(parsed_command_line_options); + + // Handle --version + if (0 != parsed_command_line_options.count("version")) { + cerr << static_cast(cVersion) << endl; + return ParsingResult::InfoCommand; + } + + // Validate command + if (parsed_command_line_options.count("command") == 0) { + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 2) { + SPDLOG_WARN("Ignoring all options besides --help."); + } + + print_basic_usage(); + cerr << "COMMAND is one of:" << endl; + cerr << " " << enum_to_underlying_type(Command::Search) << " - search" << endl; + cerr << " " << enum_to_underlying_type(Command::ExtractIr) << " - extract IR" + << endl; + cerr << endl; + cerr << "Try " << get_program_name() << " " + << enum_to_underlying_type(Command::Search) << " --help OR " + << get_program_name() << " " << enum_to_underlying_type(Command::ExtractIr) + << " --help for command-specific details." << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration " + "file." + << endl; + po::options_description visible_options; + visible_options.add(options_general); + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } + + throw invalid_argument("COMMAND not specified."); + } + switch (command_input) { + case enum_to_underlying_type(Command::Search): + m_command = static_cast(command_input); + return parse_search_arguments( + options_general, + parsed_command_line_options, + parsed.options, + argc + ); + case enum_to_underlying_type(Command::ExtractIr): + m_command = static_cast(command_input); + return parse_ir_extraction_arguments( + options_general, + parsed_command_line_options, + parsed.options, + argc + ); + default: + throw invalid_argument(string("Unknown command '") + command_input + "'"); + } + } catch (exception& e) { + SPDLOG_ERROR("{}", e.what()); + print_basic_usage(); + cerr << "Try " << get_program_name() << " --help for detailed usage instructions" << endl; + return ParsingResult::Failure; + } +} + +auto CommandLineArguments::parse_ir_extraction_arguments( + po::options_description const& options_general, + po::variables_map& parsed_command_line_options, + vector const& options, + int argc +) -> CommandLineArgumentsBase::ParsingResult { + // Define IR extraction options + po::options_description options_ir_extraction("IR Extraction Options"); + // clang-format off + options_ir_extraction + .add_options()( + "temp-output-dir", + po::value(&m_ir_temp_output_dir)->value_name("DIR"), + "Temporary output directory for IR chunks while they're being written" + )( + "target-size", + po::value(&m_ir_target_size)->value_name("SIZE"), + "Target size (B) for each IR chunk before a new chunk is created" + ); + // clang-format on + + // Define visible options + po::options_description visible_options; + visible_options.add(options_general); + visible_options.add(options_ir_extraction); + + // Define hidden positional options (not shown in Boost's program options help message) + po::options_description hidden_positional_options; + // clang-format off + hidden_positional_options.add_options()( + "archive-path", + po::value(&m_archive_path) + )( + "file-split-id", + po::value(&m_file_split_id) + )( + "output-dir", + po::value(&m_ir_output_dir) + )( + "mongodb-uri", + po::value(&m_ir_mongodb_uri) + )( + "mongodb-collection", + po::value(&m_ir_mongodb_collection) + ); + // clang-format on + po::positional_options_description positional_options_description; + positional_options_description.add("archive-path", 1); + positional_options_description.add("file-split-id", 1); + positional_options_description.add("output-dir", 1); + positional_options_description.add("mongodb-uri", 1); + positional_options_description.add("mongodb-collection", 1); + + // Aggregate all options + po::options_description all_options; + all_options.add(options_ir_extraction); + all_options.add(hidden_positional_options); + + // Parse extraction options + auto extraction_options{po::collect_unrecognized(options, po::include_positional)}; + // Erase the command from the beginning + extraction_options.erase(extraction_options.begin()); + po::store( + po::command_line_parser(extraction_options) + .options(all_options) + .positional(positional_options_description) + .run(), + parsed_command_line_options + ); + notify(parsed_command_line_options); + + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 3) { + SPDLOG_WARN("Ignoring all options besides --help."); + } + + print_ir_extraction_basic_usage(); + cerr << "Examples:" << endl; + cerr << R"( # Extract file (split) with ID "8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4" from)" + << endl; + cerr << R"( # ARCHIVE_PATH as IR into OUTPUT_DIR from ARCHIVE_PATH, and send the metadata)" + << endl; + cerr << R"( # to mongodb://127.0.0.1:27017/test result collection)" << endl; + cerr << " " << get_program_name() + << " i ARCHIVE_PATH 8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4 OUTPUT_DIR " + "mongodb://127.0.0.1:27017/test result" + << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration " + "file." + << endl; + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } + + // Validate input arguments + if (m_archive_path.empty()) { + throw invalid_argument("ARCHIVE_PATH not specified or empty."); + } + + if (m_file_split_id.empty()) { + throw invalid_argument("FILE_SPLIT_ID not specified or empty."); + } + + if (m_ir_output_dir.empty()) { + throw invalid_argument("OUTPUT_DIR not specified or empty."); + } + + if (m_ir_mongodb_uri.empty()) { + throw invalid_argument("URI not specified or empty."); + } + + if (m_ir_mongodb_collection.empty()) { + throw invalid_argument("COLLECTION not specified or empty."); + } + + if (m_ir_temp_output_dir.empty()) { + m_ir_temp_output_dir = m_ir_output_dir; + } + return ParsingResult::Success; +} + +auto CommandLineArguments::parse_search_arguments( + po::options_description const& options_general, + po::variables_map& parsed_command_line_options, + vector const& options, + int argc +) -> CommandLineArgumentsBase::ParsingResult { // Define match controls po::options_description options_match_control("Match Controls"); options_match_control.add_options()( @@ -188,242 +432,185 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { // Aggregate all options po::options_description all_options; - all_options.add(options_general); all_options.add(options_match_control); all_options.add(options_aggregation); all_options.add(hidden_positional_options); // Parse options - try { - // Parse options specified on the command line - po::parsed_options parsed = po::command_line_parser(argc, argv) - .options(all_options) - .positional(positional_options_description) - .allow_unregistered() - .run(); - po::variables_map parsed_command_line_options; - store(parsed, parsed_command_line_options); - - // Handle config-file manually since Boost won't set it until we call notify, and we can't - // call notify until we parse the config file - if (parsed_command_line_options.count("config-file")) { - config_file_path = parsed_command_line_options["config-file"].as(); - } - - // Parse options specified through the config file - // NOTE: Command line arguments will take priority over config file since they are parsed - // first and Boost doesn't replace existing options - std::ifstream config_file(config_file_path); - if (config_file.is_open()) { - // Allow unrecognized options in configuration file since some of them may be - // exclusively for clp or other applications - po::parsed_options parsed_config_file - = po::parse_config_file(config_file, all_options, true); - store(parsed_config_file, parsed_command_line_options); - config_file.close(); - } - - notify(parsed_command_line_options); - - constexpr char cNetworkOutputHandlerName[] = "network"; - constexpr char cReducerOutputHandlerName[] = "reducer"; - constexpr char cResultsCacheOutputHandlerName[] = "results-cache"; - - // Handle --help - if (parsed_command_line_options.count("help")) { - if (argc > 2) { - SPDLOG_WARN("Ignoring all options besides --help."); - } - - print_basic_usage(); - cerr << "OUTPUT_HANDLER is one of:" << endl; - cerr << " " << static_cast(cNetworkOutputHandlerName) - << " - Output to a network destination" << endl; - cerr << " " << static_cast(cResultsCacheOutputHandlerName) - << " - Output to the results cache" << endl; - cerr << " " << static_cast(cReducerOutputHandlerName) - << " - Output to the reducer" << endl; - cerr << endl; - - cerr << "Examples:" << endl; - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to )" - "a network destination" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cNetworkOutputHandlerName) - << " --host localhost --port 18000" << endl; - cerr << endl; - - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and output the results )" - "by performing a count aggregation" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cReducerOutputHandlerName) << " --count" - << " --host localhost --port 14009 --job-id 1" << endl; - cerr << endl; - - cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to)" - R"( mongodb://127.0.0.1:27017/test "result" collection )" - << endl; - cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" - << " " << static_cast(cResultsCacheOutputHandlerName) - << R"( --uri mongodb://127.0.0.1:27017/test --collection result)" << endl; - cerr << endl; - - cerr << "Options can be specified on the command line or through a configuration file." - << endl; - cerr << visible_options << endl; - return ParsingResult::InfoCommand; - } - - // Handle --version - if (parsed_command_line_options.count("version")) { - cerr << static_cast(cVersion) << endl; - return ParsingResult::InfoCommand; - } - - // Validate archive path was specified - if (m_archive_path.empty()) { - throw invalid_argument("ARCHIVE_PATH not specified or empty."); - } - - // Validate wildcard string - if (m_search_string.empty()) { - throw invalid_argument("Wildcard string not specified or empty."); + auto search_options{po::collect_unrecognized(options, po::include_positional)}; + search_options.erase(search_options.begin()); + auto parsed{po::command_line_parser(search_options) + .options(all_options) + .positional(positional_options_description) + .allow_unregistered() + .run()}; + po::store(parsed, parsed_command_line_options); + + notify(parsed_command_line_options); + + constexpr string_view cNetworkOutputHandlerName{"network"}; + constexpr string_view cReducerOutputHandlerName{"reducer"}; + constexpr string_view cResultsCacheOutputHandlerName{"results-cache"}; + + // Handle --help + if (0 != parsed_command_line_options.count("help")) { + if (argc > 3) { + SPDLOG_WARN("Ignoring all options besides --help."); } - // Validate timestamp range and compute m_search_begin_ts and m_search_end_ts - if (parsed_command_line_options.count("teq")) { - if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") - + parsed_command_line_options.count("tlt") - + parsed_command_line_options.count("tle") - > 0) - { - throw invalid_argument( - "--teq cannot be specified with any other timestamp filtering option." - ); - } - - m_search_begin_ts = parsed_command_line_options["teq"].as(); - m_search_end_ts = parsed_command_line_options["teq"].as(); - } else { - if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") - > 1) - { - throw invalid_argument("--tgt cannot be used with --tge."); - } - - // Set m_search_begin_ts - if (parsed_command_line_options.count("tgt")) { - m_search_begin_ts = parsed_command_line_options["tgt"].as() + 1; - } else if (parsed_command_line_options.count("tge")) { - m_search_begin_ts = parsed_command_line_options["tge"].as(); - } + print_search_basic_usage(); + cerr << "OUTPUT_HANDLER is one of:" << endl; + cerr << " " << cNetworkOutputHandlerName << " - Output to a network destination" << endl; + cerr << " " << cResultsCacheOutputHandlerName << " - Output to the results cache" << endl; + cerr << " " << cReducerOutputHandlerName << " - Output to the reducer" << endl; + cerr << endl; + + cerr << "Examples:" << endl; + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to )" + "a network destination" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cNetworkOutputHandlerName << " --host localhost --port 18000" << endl; + cerr << endl; + + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and output the results )" + "by performing a count aggregation" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cReducerOutputHandlerName << " --count" + << " --host localhost --port 14009 --job-id 1" << endl; + cerr << endl; + + cerr << R"( # Search ARCHIVE_PATH for " ERROR " and send results to)" + R"( mongodb://127.0.0.1:27017/test "result" collection )" + << endl; + cerr << " " << get_program_name() << R"( ARCHIVE_PATH " ERROR ")" + << " " << cResultsCacheOutputHandlerName + << R"( --uri mongodb://127.0.0.1:27017/test --collection result)" << endl; + cerr << endl; + + cerr << "Options can be specified on the command line or through a configuration file." + << endl; + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } - if (parsed_command_line_options.count("tlt") + parsed_command_line_options.count("tle") - > 1) - { - throw invalid_argument("--tlt cannot be used with --tle."); - } + // Validate archive path was specified + if (m_archive_path.empty()) { + throw invalid_argument("ARCHIVE_PATH not specified or empty."); + } - // Set m_search_end_ts - if (parsed_command_line_options.count("tlt")) { - m_search_end_ts = parsed_command_line_options["tlt"].as() - 1; - } else if (parsed_command_line_options.count("tle")) { - m_search_end_ts = parsed_command_line_options["tle"].as(); - } + // Validate wildcard string + if (m_search_string.empty()) { + throw invalid_argument("Wildcard string not specified or empty."); + } - if (m_search_begin_ts > m_search_end_ts) { - throw invalid_argument( - "Timestamp range is invalid - begin timestamp is after end timestamp." - ); - } + // Validate timestamp range and compute m_search_begin_ts and m_search_end_ts + if (0 != parsed_command_line_options.count("teq")) { + if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") + + parsed_command_line_options.count("tlt") + + parsed_command_line_options.count("tle") + > 0) + { + throw invalid_argument( + "--teq cannot be specified with any other timestamp filtering option." + ); } - // Validate file-path - if (parsed_command_line_options.count("file-path") > 0 && m_file_path.empty()) { - throw invalid_argument("file-path cannot be an empty string."); + m_search_begin_ts = parsed_command_line_options["teq"].as(); + m_search_end_ts = parsed_command_line_options["teq"].as(); + } else { + if (parsed_command_line_options.count("tgt") + parsed_command_line_options.count("tge") > 1) + { + throw invalid_argument("--tgt cannot be used with --tge."); } - // Validate count by time bucket size - if (parsed_command_line_options.count("count-by-time") > 0) { - m_do_count_by_time_aggregation = true; - if (m_count_by_time_bucket_size <= 0) { - throw std::invalid_argument("Value for count-by-time must be greater than zero."); - } + // Set m_search_begin_ts + if (0 != parsed_command_line_options.count("tgt")) { + m_search_begin_ts = parsed_command_line_options["tgt"].as() + 1; + } else if (0 != parsed_command_line_options.count("tge")) { + m_search_begin_ts = parsed_command_line_options["tge"].as(); } - // Validate output-handler - if (parsed_command_line_options.count("output-handler") == 0) { - throw invalid_argument("OUTPUT_HANDLER not specified."); - } - if (static_cast(cNetworkOutputHandlerName) == output_handler_name) { - m_output_handler_type = OutputHandlerType::Network; - } else if (static_cast(cReducerOutputHandlerName) == output_handler_name) { - m_output_handler_type = OutputHandlerType::Reducer; - } else if (static_cast(cResultsCacheOutputHandlerName) == output_handler_name) + if (parsed_command_line_options.count("tlt") + parsed_command_line_options.count("tle") > 1) { - m_output_handler_type = OutputHandlerType::ResultsCache; - } else if (output_handler_name.empty()) { - throw invalid_argument("OUTPUT_HANDLER cannot be an empty string."); - } else { - throw invalid_argument("Unknown OUTPUT_HANDLER: " + output_handler_name); + throw invalid_argument("--tlt cannot be used with --tle."); } - switch (m_output_handler_type) { - case OutputHandlerType::Network: - parse_network_dest_output_handler_options( - options_network_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - case OutputHandlerType::Reducer: - parse_reducer_output_handler_options( - options_reducer_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - case OutputHandlerType::ResultsCache: - parse_results_cache_output_handler_options( - options_results_cache_output_handler, - parsed.options, - parsed_command_line_options - ); - break; - default: - throw invalid_argument( - "Unhandled OutputHandlerType=" - + std::to_string(enum_to_underlying_type(m_output_handler_type)) - ); + // Set m_search_end_ts + if (0 != parsed_command_line_options.count("tlt")) { + m_search_end_ts = parsed_command_line_options["tlt"].as() - 1; + } else if (0 != parsed_command_line_options.count("tle")) { + m_search_end_ts = parsed_command_line_options["tle"].as(); } - bool aggregation_was_specified - = m_do_count_by_time_aggregation || m_do_count_results_aggregation; - if (aggregation_was_specified && OutputHandlerType::Reducer != m_output_handler_type) { + if (m_search_begin_ts > m_search_end_ts) { throw invalid_argument( - "Aggregations are only supported with the reducer output handler." + "Timestamp range is invalid - begin timestamp is after end timestamp." ); - } else if ((false == aggregation_was_specified - && OutputHandlerType::Reducer == m_output_handler_type)) - { - throw invalid_argument("The reducer output handler currently only supports count and " - "count-by-time aggregations."); } + } - if (m_do_count_by_time_aggregation && m_do_count_results_aggregation) { - throw std::invalid_argument( - "The --count-by-time and --count options are mutually exclusive." - ); + // Validate file-path + if (parsed_command_line_options.count("file-path") > 0 && m_file_path.empty()) { + throw invalid_argument("file-path cannot be an empty string."); + } + + // Validate count by time bucket size + if (parsed_command_line_options.count("count-by-time") > 0) { + m_do_count_by_time_aggregation = true; + if (m_count_by_time_bucket_size <= 0) { + throw std::invalid_argument("Value for count-by-time must be greater than zero."); } - } catch (exception& e) { - SPDLOG_ERROR("{}", e.what()); - print_basic_usage(); - cerr << "Try " << get_program_name() << " --help for detailed usage instructions" << endl; - return ParsingResult::Failure; } + // Validate output-handler + if (parsed_command_line_options.count("output-handler") == 0) { + throw invalid_argument("OUTPUT_HANDLER not specified."); + } + if (cNetworkOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::Network; + parse_network_dest_output_handler_options( + options_network_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (cReducerOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::Reducer; + parse_reducer_output_handler_options( + options_reducer_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (cResultsCacheOutputHandlerName == output_handler_name) { + m_output_handler_type = OutputHandlerType::ResultsCache; + parse_results_cache_output_handler_options( + options_results_cache_output_handler, + parsed.options, + parsed_command_line_options + ); + } else if (output_handler_name.empty()) { + throw invalid_argument("OUTPUT_HANDLER cannot be an empty string."); + } else { + throw invalid_argument("Unknown OUTPUT_HANDLER: " + output_handler_name); + } + + bool const aggregation_was_specified + = m_do_count_by_time_aggregation || m_do_count_results_aggregation; + if (aggregation_was_specified && OutputHandlerType::Reducer != m_output_handler_type) { + throw invalid_argument("Aggregations are only supported with the reducer output handler."); + } + if ((false == aggregation_was_specified && OutputHandlerType::Reducer == m_output_handler_type)) + { + throw invalid_argument("The reducer output handler currently only supports count and " + "count-by-time aggregations."); + } + + if (m_do_count_by_time_aggregation && m_do_count_results_aggregation) { + throw std::invalid_argument( + "The --count-by-time and --count options are mutually exclusive." + ); + } return ParsingResult::Success; } @@ -511,7 +698,18 @@ void CommandLineArguments::parse_results_cache_output_handler_options( } void CommandLineArguments::print_basic_usage() const { - cerr << "Usage: " << get_program_name() << " [OPTIONS]" - << R"( ARCHIVE_PATH "WILDCARD STRING" OUTPUT_HANDLER [OUTPUT_HANDLER_OPTIONS])" << endl; + cerr << "Usage: " << get_program_name() << " [OPTIONS] COMMAND [COMMAND ARGUMENTS]" << endl; +} + +void CommandLineArguments::print_search_basic_usage() const { + cerr << "Usage: " << get_program_name() << " " << enum_to_underlying_type(Command::Search) + << R"( [OPTIONS] ARCHIVE_PATH "WILDCARD STRING" OUTPUT_HANDLER [OUTPUT_HANDLER_OPTIONS])" + << endl; +} + +void CommandLineArguments::print_ir_extraction_basic_usage() const { + cerr << "Usage: " << get_program_name() << " " << enum_to_underlying_type(Command::ExtractIr) + << R"( [OPTIONS] ARCHIVE_PATH FILE_SPLIT_ID OUTPUT_DIR MONGODB_URI MONGODB_COLLECTION)" + << endl; } } // namespace clp::clo diff --git a/components/core/src/clp/clo/CommandLineArguments.hpp b/components/core/src/clp/clo/CommandLineArguments.hpp index 16c88bf26..9e6d311c3 100644 --- a/components/core/src/clp/clo/CommandLineArguments.hpp +++ b/components/core/src/clp/clo/CommandLineArguments.hpp @@ -1,8 +1,10 @@ #ifndef CLP_CLO_COMMANDLINEARGUMENTS_HPP #define CLP_CLO_COMMANDLINEARGUMENTS_HPP +#include #include #include +#include #include #include @@ -18,6 +20,11 @@ namespace clp::clo { class CommandLineArguments : public CommandLineArgumentsBase { public: // Types + enum class Command : char { + Search = 's', + ExtractIr = 'i', + }; + enum class OutputHandlerType : uint8_t { Network = 0, Reducer, @@ -36,8 +43,28 @@ class CommandLineArguments : public CommandLineArgumentsBase { // Methods ParsingResult parse_arguments(int argc, char const* argv[]) override; - std::string const& get_archive_path() const { return m_archive_path; } + [[nodiscard]] auto get_command() const -> Command { return m_command; } + + [[nodiscard]] auto get_archive_path() const -> std::string_view { return m_archive_path; } + + // IR extraction arguments + [[nodiscard]] auto get_file_split_id() const -> std::string const& { return m_file_split_id; } + + [[nodiscard]] size_t get_ir_target_size() const { return m_ir_target_size; } + [[nodiscard]] auto get_ir_output_dir() const -> std::string const& { return m_ir_output_dir; } + + [[nodiscard]] auto get_ir_temp_output_dir() const -> std::string const& { + return m_ir_temp_output_dir; + } + + [[nodiscard]] auto get_ir_mongodb_uri() const -> std::string const& { return m_ir_mongodb_uri; } + + [[nodiscard]] auto get_ir_mongodb_collection() const -> std::string const& { + return m_ir_mongodb_collection; + } + + // Search arguments bool ignore_case() const { return m_ignore_case; } std::string const& get_search_string() const { return m_search_string; } @@ -76,6 +103,40 @@ class CommandLineArguments : public CommandLineArgumentsBase { private: // Methods + /** + * Parses arguments for the search command + * @param options_general + * @param parsed_command_line_options + * @param options + * @param argc + * @return ParsingResult::Success if arguments were parsed without error + * @return ParsingResult::InfoCommand if `--help` was specified + * @throw invalid_argument if invalid arguments are provided + */ + [[nodiscard]] auto parse_search_arguments( + boost::program_options::options_description const& options_general, + boost::program_options::variables_map& parsed_command_line_options, + std::vector const& options, + int argc + ) -> CommandLineArgumentsBase::ParsingResult; + + /** + * Parses arguments for the IR extraction command + * @param options_general + * @param parsed_command_line_options + * @param options + * @param argc + * @return ParsingResult::Success if arguments were parsed without error + * @return ParsingResult::InfoCommand if `--help` was specified + * @throw invalid_argument if invalid arguments are provided + */ + [[nodiscard]] auto parse_ir_extraction_arguments( + boost::program_options::options_description const& options_general, + boost::program_options::variables_map& parsed_command_line_options, + std::vector const& options, + int argc + ) -> CommandLineArgumentsBase::ParsingResult; + /** * Validates output options related to the Network Destination output handler. * @param options_description @@ -116,9 +177,21 @@ class CommandLineArguments : public CommandLineArgumentsBase { ); void print_basic_usage() const override; + void print_search_basic_usage() const; + void print_ir_extraction_basic_usage() const; - // Search variables + Command m_command; std::string m_archive_path; + + // Variables for IR extraction + std::string m_file_split_id; + size_t m_ir_target_size{128ULL * 1024 * 1024}; + std::string m_ir_output_dir; + std::string m_ir_temp_output_dir; + std::string m_ir_mongodb_uri; + std::string m_ir_mongodb_collection; + + // Variables for search bool m_ignore_case; std::string m_search_string; std::string m_file_path; diff --git a/components/core/src/clp/clo/OutputHandler.cpp b/components/core/src/clp/clo/OutputHandler.cpp index 1270b204c..bdf1bb1bd 100644 --- a/components/core/src/clp/clo/OutputHandler.cpp +++ b/components/core/src/clp/clo/OutputHandler.cpp @@ -1,7 +1,8 @@ #include "OutputHandler.hpp" #include -#include +#include +#include #include #include @@ -9,9 +10,14 @@ #include "../../reducer/CountOperator.hpp" #include "../../reducer/network_utils.hpp" #include "../networking/socket_utils.hpp" +#include "constants.hpp" + +using clp::streaming_archive::reader::Message; +using std::string; +using std::string_view; namespace clp::clo { -NetworkOutputHandler::NetworkOutputHandler(std::string const& host, int port) { +NetworkOutputHandler::NetworkOutputHandler(string const& host, int port) { m_socket_fd = clp::networking::connect_to_server(host, std::to_string(port)); if (-1 == m_socket_fd) { SPDLOG_ERROR("Failed to connect to the server"); @@ -20,14 +26,17 @@ NetworkOutputHandler::NetworkOutputHandler(std::string const& host, int port) { } ErrorCode NetworkOutputHandler::add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + string_view orig_file_path, + string_view orig_file_id, + Message const& encoded_message, + string_view decompressed_message ) { - msgpack::type::tuple src( - original_path, - timestamp, - message + msgpack::type::tuple src( + encoded_message.get_ts_in_milli(), + decompressed_message, + orig_file_path, + orig_file_id, + encoded_message.get_log_event_ix() ); msgpack::sbuffer m; msgpack::pack(m, src); @@ -35,8 +44,8 @@ ErrorCode NetworkOutputHandler::add_result( } ResultsCacheOutputHandler::ResultsCacheOutputHandler( - std::string const& uri, - std::string const& collection, + string const& uri, + string const& collection, uint64_t batch_size, uint64_t max_num_results ) @@ -53,15 +62,30 @@ ResultsCacheOutputHandler::ResultsCacheOutputHandler( } ErrorCode ResultsCacheOutputHandler::add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + string_view orig_file_path, + string_view orig_file_id, + Message const& encoded_message, + string_view decompressed_message ) { + auto const timestamp = encoded_message.get_ts_in_milli(); + auto const log_event_ix = encoded_message.get_log_event_ix(); if (m_latest_results.size() < m_max_num_results) { - m_latest_results.emplace(std::make_unique(original_path, message, timestamp)); + m_latest_results.emplace(std::make_unique( + orig_file_path, + orig_file_id, + log_event_ix, + timestamp, + decompressed_message + )); } else if (m_latest_results.top()->timestamp < timestamp) { m_latest_results.pop(); - m_latest_results.emplace(std::make_unique(original_path, message, timestamp)); + m_latest_results.emplace(std::make_unique( + orig_file_path, + orig_file_id, + log_event_ix, + timestamp, + decompressed_message + )); } return ErrorCode_Success; @@ -75,9 +99,26 @@ ErrorCode ResultsCacheOutputHandler::flush() { try { m_results.emplace_back(std::move(bsoncxx::builder::basic::make_document( - bsoncxx::builder::basic::kvp("original_path", std::move(result.original_path)), - bsoncxx::builder::basic::kvp("message", std::move(result.message)), - bsoncxx::builder::basic::kvp("timestamp", result.timestamp) + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::OrigFileId, + std::move(result.orig_file_id) + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::OrigFilePath, + std::move(result.orig_file_path) + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::LogEventIx, + result.log_event_ix + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::Timestamp, + result.timestamp + ), + bsoncxx::builder::basic::kvp( + cResultsCacheKeys::SearchOutput::Message, + std::move(result.decompressed_message) + ) ))); count++; @@ -109,9 +150,10 @@ CountOutputHandler::CountOutputHandler(int reducer_socket_fd) } ErrorCode CountOutputHandler::add_result( - [[maybe_unused]] std::string const& original_path, - [[maybe_unused]] std::string const& message, - [[maybe_unused]] epochtime_t timestamp + [[maybe_unused]] string_view orig_file_path, + [[maybe_unused]] string_view orig_file_id, + [[maybe_unused]] Message const& encoded_message, + [[maybe_unused]] string_view decompressed_message ) { m_pipeline.push_record(reducer::EmptyRecord{}); return ErrorCode_Success; diff --git a/components/core/src/clp/clo/OutputHandler.hpp b/components/core/src/clp/clo/OutputHandler.hpp index e9a7c8069..5f8b71f7d 100644 --- a/components/core/src/clp/clo/OutputHandler.hpp +++ b/components/core/src/clp/clo/OutputHandler.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -14,6 +15,7 @@ #include "../../reducer/Pipeline.hpp" #include "../Defs.h" #include "../streaming_archive/MetadataDB.hpp" +#include "../streaming_archive/reader/Message.hpp" #include "../TraceableException.hpp" namespace clp::clo { @@ -28,14 +30,19 @@ class OutputHandler { // Methods /** * Adds a query result to a batch or sends it to the destination. - * @param original_path The original path of the log event. - * @param message The content of the log event. - * @param timestamp The timestamp of the log event. - * @return ErrorCode_Success if the result was added successfully, an error code otherwise. + * @param orig_file_path Path of the original file that contains the result. + * @param orig_file_id ID of the original file that contains the result. + * @param encoded_message The encoded result. + * @param decompressed_message The decompressed result. + * @return ErrorCode_Success if the result was added successfully, or an error code if specified + * by the derived class. */ - virtual ErrorCode - add_result(std::string const& original_path, std::string const& message, epochtime_t timestamp) - = 0; + virtual ErrorCode add_result( + std::string_view orig_file_path, + std::string_view orig_file_id, + streaming_archive::reader::Message const& encoded_message, + std::string_view decompressed_message + ) = 0; /** * Flushes any buffered output. Called once at the end of search. @@ -49,7 +56,7 @@ class OutputHandler { * metadata about the file */ [[nodiscard]] virtual bool can_skip_file( - [[maybe_unused]] clp::streaming_archive::MetadataDB::FileIterator const& it + [[maybe_unused]] ::clp::streaming_archive::MetadataDB::FileIterator const& it ) { return false; } @@ -82,15 +89,17 @@ class NetworkOutputHandler : public OutputHandler { // Methods inherited from Client /** * Sends a result to the network destination. - * @param original_path - * @param message - * @param timestamp + * @param orig_file_path Path of the original file that contains the result. + * @param orig_file_id ID of the original file that contains the result. + * @param encoded_message The encoded result. + * @param decompressed_message The decompressed result. * @return Same as networking::try_send */ ErrorCode add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + std::string_view orig_file_path, + std::string_view orig_file_id, + streaming_archive::reader::Message const& encoded_message, + std::string_view decompressed_message ) override; /** @@ -114,14 +123,24 @@ class ResultsCacheOutputHandler : public OutputHandler { // Types struct QueryResult { // Constructors - QueryResult(std::string original_path, std::string message, epochtime_t timestamp) - : original_path(std::move(original_path)), - message(std::move(message)), - timestamp(timestamp) {} - - std::string original_path; - std::string message; + QueryResult( + std::string_view orig_file_path, + std::string_view orig_file_id, + size_t log_event_ix, + epochtime_t timestamp, + std::string_view decompressed_message + ) + : orig_file_path(orig_file_path), + orig_file_id(orig_file_id), + log_event_ix(log_event_ix), + timestamp(timestamp), + decompressed_message(decompressed_message) {} + + std::string orig_file_path; + std::string orig_file_id; + int64_t log_event_ix; epochtime_t timestamp; + std::string decompressed_message; }; struct QueryResultGreaterTimestampComparator { @@ -154,17 +173,11 @@ class ResultsCacheOutputHandler : public OutputHandler { ); // Methods inherited from OutputHandler - /** - * Adds a result to the batch. - * @param original_path - * @param message - * @param timestamp - * @return ErrorCode_Success - */ ErrorCode add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + std::string_view orig_file_path, + std::string_view orig_file_id, + streaming_archive::reader::Message const& encoded_message, + std::string_view decompressed_message ) override; /** @@ -174,7 +187,7 @@ class ResultsCacheOutputHandler : public OutputHandler { */ ErrorCode flush() override; - [[nodiscard]] bool can_skip_file(clp::streaming_archive::MetadataDB::FileIterator const& it + [[nodiscard]] bool can_skip_file(::clp::streaming_archive::MetadataDB::FileIterator const& it ) override { return is_latest_results_full() && get_smallest_timestamp() > it.get_end_ts(); } @@ -216,17 +229,11 @@ class CountOutputHandler : public OutputHandler { explicit CountOutputHandler(int reducer_socket_fd); // Methods inherited from OutputHandler - /** - * Adds a result. - * @param original_path - * @param message - * @param timestamp - * @return ErrorCode_Success - */ ErrorCode add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + std::string_view orig_file_path, + std::string_view orig_file_id, + streaming_archive::reader::Message const& encoded_message, + std::string_view decompressed_message ) override; /** @@ -254,11 +261,13 @@ class CountByTimeOutputHandler : public OutputHandler { // Methods inherited from OutputHandler ErrorCode add_result( - std::string const& original_path, - std::string const& message, - epochtime_t timestamp + std::string_view orig_file_path, + std::string_view orig_file_id, + streaming_archive::reader::Message const& encoded_message, + std::string_view decompressed_message ) override { - int64_t bucket = (timestamp / m_count_by_time_bucket_size) * m_count_by_time_bucket_size; + int64_t bucket = (encoded_message.get_ts_in_milli() / m_count_by_time_bucket_size) + * m_count_by_time_bucket_size; m_bucket_counts[bucket] += 1; return ErrorCode::ErrorCode_Success; } diff --git a/components/core/src/clp/clo/clo.cpp b/components/core/src/clp/clo/clo.cpp index 2344f7c84..07c29c308 100644 --- a/components/core/src/clp/clo/clo.cpp +++ b/components/core/src/clp/clo/clo.cpp @@ -1,16 +1,21 @@ +#include #include #include +#include -#include #include #include #include "../../reducer/network_utils.hpp" +#include "../clp/FileDecompressor.hpp" #include "../Defs.h" #include "../Grep.hpp" +#include "../ir/constants.hpp" #include "../Profiler.hpp" #include "../spdlog_with_specializations.hpp" +#include "../Utils.hpp" #include "CommandLineArguments.hpp" +#include "constants.hpp" #include "OutputHandler.hpp" using clp::clo::CommandLineArguments; @@ -19,12 +24,17 @@ using clp::clo::CountOutputHandler; using clp::clo::NetworkOutputHandler; using clp::clo::OutputHandler; using clp::clo::ResultsCacheOutputHandler; +using clp::clp::FileDecompressor; using clp::CommandLineArgumentsBase; +using clp::create_directory; using clp::epochtime_t; using clp::ErrorCode; +using clp::ErrorCode_BadParam_DB_URI; using clp::ErrorCode_errno; +using clp::ErrorCode_FileExists; using clp::ErrorCode_Success; using clp::Grep; +using clp::ir::cIrFileExtension; using clp::load_lexer_from_file; using clp::Query; using clp::streaming_archive::MetadataDB; @@ -35,6 +45,7 @@ using clp::TraceableException; using std::cerr; using std::cout; using std::endl; +using std::runtime_error; using std::string; using std::to_string; using std::unique_ptr; @@ -81,16 +92,279 @@ static void search_files( /** * Searches an archive with the given path * @param command_line_args - * @param archive_path * @param output_handler * @return true on success, false otherwise */ static bool search_archive( CommandLineArguments const& command_line_args, - boost::filesystem::path const& archive_path, std::unique_ptr output_handler ); +namespace { +/** + * Extracts a file split as IR chunks, writing them to the local filesystem and writing their + * metadata to the results cache. + * @param command_line_args + * @return Whether the file split was successfully extracted. + */ +bool extract_ir(CommandLineArguments const& command_line_args); + +/** + * Performs a searches acccording to the given arguments. + * @param command_line_args + * @return Whether the search was successful. + */ +bool search(CommandLineArguments const& command_line_args); + +/** + * @param archive_path + * @return Whether the given path exists and contains an archive metadata file. + */ +bool validate_archive_path(std::filesystem::path const& archive_path); + +bool extract_ir(CommandLineArguments const& command_line_args) { + std::filesystem::path const archive_path{command_line_args.get_archive_path()}; + if (false == validate_archive_path(archive_path)) { + return false; + } + + try { + // Create output directory in case it doesn't exist + std::filesystem::path const output_dir{command_line_args.get_ir_output_dir()}; + if (auto const error_code = create_directory(output_dir.string(), 0700, true); + ErrorCode_Success != error_code) + { + SPDLOG_ERROR("Failed to create {} - {}", output_dir.string(), strerror(errno)); + return false; + } + + Archive archive_reader; + archive_reader.open(archive_path.string()); + archive_reader.refresh_dictionaries(); + + auto const& file_split_id = command_line_args.get_file_split_id(); + auto file_metadata_ix_ptr = archive_reader.get_file_iterator_by_split_id(file_split_id); + if (false == file_metadata_ix_ptr->has_next()) { + SPDLOG_ERROR( + "File split '{}' doesn't exist in archive '{}'", + file_split_id, + archive_path.string() + ); + return false; + } + + mongocxx::client client; + mongocxx::collection collection; + + try { + auto const mongo_uri{mongocxx::uri(command_line_args.get_ir_mongodb_uri())}; + client = mongocxx::client{mongo_uri}; + collection + = client[mongo_uri.database()][command_line_args.get_ir_mongodb_collection()]; + } catch (mongocxx::exception const& e) { + SPDLOG_ERROR("Failed to connect to results cache - {}", e.what()); + return false; + } + + std::vector results; + auto ir_output_handler = [&](std::filesystem::path const& src_ir_path, + string const& orig_file_id, + size_t begin_message_ix, + size_t end_message_ix, + bool is_last_ir_chunk) { + auto dest_ir_file_name = orig_file_id; + dest_ir_file_name += "_" + std::to_string(begin_message_ix); + dest_ir_file_name += "_" + std::to_string(end_message_ix); + dest_ir_file_name += cIrFileExtension; + + auto const dest_ir_path = output_dir / dest_ir_file_name; + try { + std::filesystem::rename(src_ir_path, dest_ir_path); + } catch (std::filesystem::filesystem_error const& e) { + SPDLOG_ERROR( + "Failed to rename '{}' to '{}' - {}", + src_ir_path.string(), + dest_ir_path.string(), + e.what() + ); + return false; + } + results.emplace_back(std::move(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::Path, + dest_ir_file_name + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::OrigFileId, + orig_file_id + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::FileSplitId, + file_split_id + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::BeginMsgIx, + static_cast(begin_message_ix) + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::EndMsgIx, + static_cast(end_message_ix) + ), + bsoncxx::builder::basic::kvp( + clp::clo::cResultsCacheKeys::IrOutput::IsLastIrChunk, + is_last_ir_chunk + ) + ))); + return true; + }; + + FileDecompressor file_decompressor; + if (false + == file_decompressor.decompress_to_ir( + archive_reader, + *file_metadata_ix_ptr, + command_line_args.get_ir_target_size(), + command_line_args.get_ir_temp_output_dir(), + ir_output_handler + )) + { + return false; + } + + // Write the metadata into the results cache + if (false == results.empty()) { + try { + collection.insert_many(results); + } catch (mongocxx::exception const& e) { + SPDLOG_ERROR("Failed to insert results into results cache - {}", e.what()); + return false; + } + results.clear(); + } + + file_metadata_ix_ptr.reset(nullptr); + + archive_reader.close(); + } catch (TraceableException& e) { + auto error_code = e.get_error_code(); + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR( + "IR extraction failed: {}:{} {}, errno={}", + e.get_filename(), + e.get_line_number(), + e.what(), + errno + ); + } else { + SPDLOG_ERROR( + "IR extraction failed: {}:{} {}, error_code={}", + e.get_filename(), + e.get_line_number(), + e.what(), + error_code + ); + } + return false; + } + + return true; +} + +bool search(CommandLineArguments const& command_line_args) { + std::unique_ptr output_handler; + try { + switch (command_line_args.get_output_handler_type()) { + case CommandLineArguments::OutputHandlerType::Network: + output_handler = std::make_unique( + command_line_args.get_network_dest_host(), + command_line_args.get_network_dest_port() + ); + break; + case CommandLineArguments::OutputHandlerType::Reducer: { + auto const reducer_socket_fd = reducer::connect_to_reducer( + command_line_args.get_reducer_host(), + command_line_args.get_reducer_port(), + command_line_args.get_job_id() + ); + if (-1 == reducer_socket_fd) { + SPDLOG_ERROR("Failed to connect to reducer"); + return false; + } + + if (command_line_args.do_count_results_aggregation()) { + output_handler = std::make_unique(reducer_socket_fd); + } else if (command_line_args.do_count_by_time_aggregation()) { + output_handler = std::make_unique( + reducer_socket_fd, + command_line_args.get_count_by_time_bucket_size() + ); + } else { + SPDLOG_ERROR("Unhandled aggregation type."); + return false; + } + + break; + } + case CommandLineArguments::OutputHandlerType::ResultsCache: + output_handler = std::make_unique( + command_line_args.get_mongodb_uri(), + command_line_args.get_mongodb_collection(), + command_line_args.get_batch_size(), + command_line_args.get_max_num_results() + ); + break; + default: + SPDLOG_ERROR("Unhandled OutputHandlerType."); + return false; + } + } catch (clp::TraceableException& e) { + SPDLOG_ERROR("Failed to create output handler - {}", e.what()); + return false; + } + + try { + return search_archive(command_line_args, std::move(output_handler)); + } catch (TraceableException& e) { + auto error_code = e.get_error_code(); + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR( + "Search failed: {}:{} {}, errno={}", + e.get_filename(), + e.get_line_number(), + e.what(), + errno + ); + } else { + SPDLOG_ERROR( + "Search failed: {}:{} {}, error_code={}", + e.get_filename(), + e.get_line_number(), + e.what(), + error_code + ); + } + return false; + } +} + +bool validate_archive_path(std::filesystem::path const& archive_path) { + if (false == std::filesystem::exists(archive_path)) { + SPDLOG_ERROR("Archive '{}' doesn't exist.", archive_path.string()); + return false; + } + auto const archive_metadata_file = archive_path / clp::streaming_archive::cMetadataFileName; + if (false == std::filesystem::exists(archive_metadata_file)) { + SPDLOG_ERROR( + "Archive metadata file '{}' doesn't exist. '{}' may not be an archive.", + archive_metadata_file.string(), + archive_path.string() + ); + return false; + } + return true; +} +} // namespace + static SearchFilesResult search_file( Query& query, Archive& archive, @@ -98,7 +372,7 @@ static SearchFilesResult search_file( std::unique_ptr& output_handler ) { File compressed_file; - Message compressed_message; + Message encoded_message; string decompressed_message; ErrorCode error_code = archive.open_file(compressed_file, file_metadata_ix); @@ -119,15 +393,16 @@ static SearchFilesResult search_file( query, archive, compressed_file, - compressed_message, + encoded_message, decompressed_message )) { if (ErrorCode_Success != output_handler->add_result( compressed_file.get_orig_path(), - decompressed_message, - compressed_message.get_ts_in_milli() + compressed_file.get_orig_file_id_as_string(), + encoded_message, + decompressed_message )) { result = SearchFilesResult::ResultSendFailure; @@ -183,20 +458,10 @@ void search_files( static bool search_archive( CommandLineArguments const& command_line_args, - boost::filesystem::path const& archive_path, std::unique_ptr output_handler ) { - if (false == boost::filesystem::exists(archive_path)) { - SPDLOG_ERROR("Archive '{}' does not exist.", archive_path.c_str()); - return false; - } - auto archive_metadata_file = archive_path / clp::streaming_archive::cMetadataFileName; - if (false == boost::filesystem::exists(archive_metadata_file)) { - SPDLOG_ERROR( - "Archive metadata file '{}' does not exist. '{}' may not be an archive.", - archive_metadata_file.c_str(), - archive_path.c_str() - ); + std::filesystem::path const archive_path{command_line_args.get_archive_path()}; + if (false == validate_archive_path(archive_path)) { return false; } @@ -204,7 +469,7 @@ static bool search_archive( auto schema_file_path = archive_path / clp::streaming_archive::cSchemaFileName; unique_ptr lexer; bool use_heuristic = true; - if (boost::filesystem::exists(schema_file_path)) { + if (std::filesystem::exists(schema_file_path)) { use_heuristic = false; // Create forward lexer lexer = std::make_unique(); @@ -296,85 +561,21 @@ int main(int argc, char const* argv[]) { break; } + // mongocxx static init mongocxx::instance mongocxx_instance{}; - std::unique_ptr output_handler; - try { - switch (command_line_args.get_output_handler_type()) { - case CommandLineArguments::OutputHandlerType::Network: - output_handler = std::make_unique( - command_line_args.get_network_dest_host(), - command_line_args.get_network_dest_port() - ); - break; - case CommandLineArguments::OutputHandlerType::Reducer: { - auto reducer_socket_fd = reducer::connect_to_reducer( - command_line_args.get_reducer_host(), - command_line_args.get_reducer_port(), - command_line_args.get_job_id() - ); - if (-1 == reducer_socket_fd) { - SPDLOG_ERROR("Failed to connect to reducer"); - return -1; - } - - if (command_line_args.do_count_results_aggregation()) { - output_handler = std::make_unique(reducer_socket_fd); - } else if (command_line_args.do_count_by_time_aggregation()) { - output_handler = std::make_unique( - reducer_socket_fd, - command_line_args.get_count_by_time_bucket_size() - ); - } else { - SPDLOG_ERROR("Unhandled aggregation type."); - return -1; - } - - break; - } - case CommandLineArguments::OutputHandlerType::ResultsCache: - output_handler = std::make_unique( - command_line_args.get_mongodb_uri(), - command_line_args.get_mongodb_collection(), - command_line_args.get_batch_size(), - command_line_args.get_max_num_results() - ); - break; - default: - SPDLOG_ERROR("Unhandled OutputHandlerType."); - return -1; + auto const& command = command_line_args.get_command(); + if (CommandLineArguments::Command::Search == command) { + if (false == search(command_line_args)) { + return -1; } - } catch (clp::TraceableException& e) { - SPDLOG_ERROR("Failed to create output handler - {}", e.what()); + } else if (CommandLineArguments::Command::ExtractIr == command) { + if (false == extract_ir(command_line_args)) { + return -1; + } + } else { + SPDLOG_ERROR("Command {} not implemented.", clp::enum_to_underlying_type(command)); return -1; } - auto const archive_path = boost::filesystem::path(command_line_args.get_archive_path()); - - int return_value = 0; - try { - if (false == search_archive(command_line_args, archive_path, std::move(output_handler))) { - return_value = -1; - } - } catch (TraceableException& e) { - auto error_code = e.get_error_code(); - if (ErrorCode_errno == error_code) { - SPDLOG_ERROR( - "Search failed: {}:{} {}, errno={}", - e.get_filename(), - e.get_line_number(), - e.what(), - errno - ); - } else { - SPDLOG_ERROR( - "Search failed: {}:{} {}, error_code={}", - e.get_filename(), - e.get_line_number(), - e.what(), - error_code - ); - } - return_value = -1; - } - return return_value; + return 0; } diff --git a/components/core/src/clp/clo/constants.hpp b/components/core/src/clp/clo/constants.hpp new file mode 100644 index 000000000..86f7313f2 --- /dev/null +++ b/components/core/src/clp/clo/constants.hpp @@ -0,0 +1,26 @@ +#ifndef CLP_CLO_CONSTANTS_HPP +#define CLP_CLO_CONSTANTS_HPP + +// NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays, readability-identifier-naming) +namespace clp::clo::cResultsCacheKeys { +constexpr char OrigFileId[]{"orig_file_id"}; + +namespace IrOutput { +constexpr char Path[]{"path"}; +constexpr char FileSplitId[]{"file_split_id"}; +constexpr char BeginMsgIx[]{"begin_msg_ix"}; +constexpr char EndMsgIx[]{"end_msg_ix"}; +constexpr char IsLastIrChunk[]{"is_last_ir_chunk"}; +} // namespace IrOutput + +namespace SearchOutput { +constexpr char OrigFilePath[]{"orig_file_path"}; +constexpr char LogEventIx[]{"log_event_ix"}; +constexpr char Timestamp[]{"timestamp"}; +constexpr char Message[]{"message"}; +} // namespace SearchOutput +} // namespace clp::clo::cResultsCacheKeys + +// NOLINTEND(cppcoreguidelines-avoid-c-arrays, readability-identifier-naming) + +#endif // CLP_CLO_CONSTANTS_HPP diff --git a/components/core/src/clp/clp/CMakeLists.txt b/components/core/src/clp/clp/CMakeLists.txt index 9b6563a98..53342f3a9 100644 --- a/components/core/src/clp/clp/CMakeLists.txt +++ b/components/core/src/clp/clp/CMakeLists.txt @@ -25,6 +25,10 @@ set( ../ffi/ir_stream/decoding_methods.inc ../ffi/ir_stream/encoding_methods.cpp ../ffi/ir_stream/encoding_methods.hpp + ../ffi/ir_stream/utils.cpp + ../ffi/ir_stream/utils.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -36,9 +40,14 @@ set( ../GlobalMySQLMetadataDB.hpp ../GlobalSQLiteMetadataDB.cpp ../GlobalSQLiteMetadataDB.hpp + ../ir/constants.hpp + ../ir/EncodedTextAst.cpp + ../ir/EncodedTextAst.hpp ../ir/LogEvent.hpp ../ir/LogEventDeserializer.cpp ../ir/LogEventDeserializer.hpp + ../ir/LogEventSerializer.cpp + ../ir/LogEventSerializer.hpp ../ir/parsing.cpp ../ir/parsing.hpp ../ir/parsing.inc @@ -75,6 +84,8 @@ set( ../Query.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../SQLiteDB.cpp ../SQLiteDB.hpp @@ -124,6 +135,8 @@ set( ../TimestampPattern.hpp ../TraceableException.hpp ../type_utils.hpp + ../utf8_utils.cpp + ../utf8_utils.hpp ../Utils.cpp ../Utils.hpp ../VariableDictionaryEntry.cpp diff --git a/components/core/src/clp/clp/CommandLineArguments.cpp b/components/core/src/clp/clp/CommandLineArguments.cpp index 169022524..ccdc99793 100644 --- a/components/core/src/clp/clp/CommandLineArguments.cpp +++ b/components/core/src/clp/clp/CommandLineArguments.cpp @@ -143,9 +143,11 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { cerr << "COMMAND is one of:" << endl; cerr << " c - compress" << endl; cerr << " x - extract" << endl; + cerr << " i - extract IR" << endl; cerr << endl; cerr << "Try " << get_program_name() << " c --help OR " << get_program_name() - << " x --help for command-specific details." << endl; + << " x --help OR " << get_program_name() + << " i --help for command-specific details." << endl; cerr << endl; cerr << "Options can be specified on the command line or through a configuration " @@ -163,6 +165,7 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { switch (command_input) { case (char)Command::Compress: case (char)Command::Extract: + case (char)Command::ExtractIr: m_command = (Command)command_input; break; default: @@ -223,6 +226,95 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { if (m_archives_dir.empty()) { throw invalid_argument("ARCHIVES_DIR cannot be empty."); } + } else if (Command::ExtractIr == m_command) { + // Define IR extraction hidden positional options + po::options_description ir_positional_options; + // clang-format off + ir_positional_options.add_options() + ("archives-dir", po::value(&m_archives_dir)) + ("output-dir", po::value(&m_output_dir)) + ("orig-file-id", po::value(&m_orig_file_id)); + // clang-format on + po::positional_options_description ir_positional_options_description; + ir_positional_options_description.add("archives-dir", 1); + ir_positional_options_description.add("output-dir", 1); + ir_positional_options_description.add("orig-file-id", 1); + + po::options_description options_ir("IR extraction Options"); + options_ir.add_options()( + "msg-ix", + po::value(&m_ir_msg_ix) + ->value_name("INDEX") + ->default_value(m_ir_msg_ix), + "Index of log event that decompressed IR chunks must include" + ); + options_ir.add_options()( + "target-size", + po::value(&m_ir_target_size) + ->value_name("SIZE") + ->default_value(m_ir_target_size), + "Target size (B) for each IR chunk before a new chunk is created" + ); + options_ir.add_options()( + "temp-output-dir", + po::value(&m_ir_temp_output_dir) + ->value_name("DIR") + ->default_value(m_ir_temp_output_dir), + "Temporary output directory for IR chunks while they're being written" + ); + + po::options_description all_ir_options; + all_ir_options.add(ir_positional_options); + all_ir_options.add(options_ir); + + // Parse IR extraction options + vector unrecognized_options + = po::collect_unrecognized(parsed.options, po::include_positional); + unrecognized_options.erase(unrecognized_options.begin()); + po::store( + po::command_line_parser(unrecognized_options) + .options(all_ir_options) + .positional(ir_positional_options_description) + .run(), + parsed_command_line_options + ); + + notify(parsed_command_line_options); + + // Handle --help + if (parsed_command_line_options.count("help")) { + print_ir_basic_usage(); + + cerr << "Examples:" << endl; + cerr << " # Extract (original) file with ID 8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4" + " as IR" + << endl; + cerr << " " << get_program_name() + << " i archives-dir output-dir 8cf8d8f2-bf3f-42a2-90b2-6bc4ed0a36b4" << endl; + cerr << endl; + + po::options_description visible_options; + visible_options.add(options_general); + visible_options.add(options_ir); + cerr << visible_options << endl; + return ParsingResult::InfoCommand; + } + + if (m_archives_dir.empty()) { + throw invalid_argument("ARCHIVES_DIR cannot be empty."); + } + + if (m_output_dir.empty()) { + throw invalid_argument("OUTPUT_DIR cannot be empty."); + } + + if (m_orig_file_id.empty()) { + throw invalid_argument("ORIG_FILE_ID cannot be empty."); + } + + if (m_ir_temp_output_dir.empty()) { + m_ir_temp_output_dir = m_output_dir; + } } else if (Command::Compress == m_command) { // Define compression hidden positional options po::options_description compression_positional_options; @@ -403,4 +495,9 @@ void CommandLineArguments::print_extraction_basic_usage() const { cerr << "Usage: " << get_program_name() << " [OPTIONS] x ARCHIVES_DIR OUTPUT_DIR [FILE ...]" << endl; } + +void CommandLineArguments::print_ir_basic_usage() const { + cerr << "Usage: " << get_program_name() << " [OPTIONS] i ARCHIVES_DIR OUTPUT_DIR ORIG_FILE_ID" + << endl; +} } // namespace clp::clp diff --git a/components/core/src/clp/clp/CommandLineArguments.hpp b/components/core/src/clp/clp/CommandLineArguments.hpp index 2634b51eb..b9cf15740 100644 --- a/components/core/src/clp/clp/CommandLineArguments.hpp +++ b/components/core/src/clp/clp/CommandLineArguments.hpp @@ -16,6 +16,7 @@ class CommandLineArguments : public CommandLineArgumentsBase { enum class Command : char { Compress = 'c', Extract = 'x', + ExtractIr = 'i' }; // Constructors @@ -36,6 +37,8 @@ class CommandLineArguments : public CommandLineArgumentsBase { std::string const& get_path_prefix_to_remove() const { return m_path_prefix_to_remove; } + std::string const& get_ir_temp_output_dir() const { return m_ir_temp_output_dir; } + std::string const& get_output_dir() const { return m_output_dir; } std::string const& get_schema_file_path() const { return m_schema_file_path; } @@ -66,6 +69,12 @@ class CommandLineArguments : public CommandLineArgumentsBase { std::vector const& get_input_paths() const { return m_input_paths; } + std::string const& get_orig_file_id() const { return m_orig_file_id; } + + size_t get_ir_msg_ix() const { return m_ir_msg_ix; } + + size_t get_ir_target_size() const { return m_ir_target_size; } + GlobalMetadataDBConfig const& get_metadata_db_config() const { return m_metadata_db_config; } private: @@ -73,11 +82,16 @@ class CommandLineArguments : public CommandLineArgumentsBase { void print_basic_usage() const override; void print_compression_basic_usage() const; void print_extraction_basic_usage() const; + void print_ir_basic_usage() const; // Variables std::string m_path_list_path; std::string m_path_prefix_to_remove; + std::string m_orig_file_id; + size_t m_ir_msg_ix{0}; + size_t m_ir_target_size{128ULL * 1024 * 1024}; bool m_sort_input_files; + std::string m_ir_temp_output_dir; std::string m_output_dir; std::string m_schema_file_path; bool m_show_progress; diff --git a/components/core/src/clp/clp/FileCompressor.cpp b/components/core/src/clp/clp/FileCompressor.cpp index 4100816f5..9898602cc 100644 --- a/components/core/src/clp/clp/FileCompressor.cpp +++ b/components/core/src/clp/clp/FileCompressor.cpp @@ -16,6 +16,7 @@ #include "../LogSurgeonReader.hpp" #include "../Profiler.hpp" #include "../streaming_archive/writer/utils.hpp" +#include "../utf8_utils.hpp" #include "utils.hpp" using clp::ir::eight_byte_encoded_variable_t; @@ -145,8 +146,8 @@ bool FileCompressor::compress_file( size_t peek_size{0}; m_file_reader.peek_buffered_data(utf8_validation_buf, peek_size); bool succeeded = true; - auto utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); - if (is_utf8_sequence(utf8_validation_buf_len, utf8_validation_buf)) { + auto const utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); + if (is_utf8_encoded({utf8_validation_buf, utf8_validation_buf_len})) { if (use_heuristic) { parse_and_encode_with_heuristic( target_data_size_of_dicts, @@ -359,8 +360,8 @@ bool FileCompressor::try_compressing_as_archive( size_t peek_size{0}; m_libarchive_file_reader.peek_buffered_data(utf8_validation_buf, peek_size); string file_path{m_libarchive_reader.get_path()}; - auto utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); - if (is_utf8_sequence(utf8_validation_buf_len, utf8_validation_buf)) { + auto const utf8_validation_buf_len = std::min(peek_size, cUtfMaxValidationLen); + if (is_utf8_encoded({utf8_validation_buf, utf8_validation_buf_len})) { auto boost_path_for_compression = parent_boost_path / file_path; if (use_heuristic) { parse_and_encode_with_heuristic( diff --git a/components/core/src/clp/clp/FileDecompressor.cpp b/components/core/src/clp/clp/FileDecompressor.cpp index 55e53258c..5e023c2e1 100644 --- a/components/core/src/clp/clp/FileDecompressor.cpp +++ b/components/core/src/clp/clp/FileDecompressor.cpp @@ -3,8 +3,6 @@ #include #include -#include "../spdlog_with_specializations.hpp" - using std::string; namespace clp::clp { diff --git a/components/core/src/clp/clp/FileDecompressor.hpp b/components/core/src/clp/clp/FileDecompressor.hpp index 51598a9f4..932cab7c5 100644 --- a/components/core/src/clp/clp/FileDecompressor.hpp +++ b/components/core/src/clp/clp/FileDecompressor.hpp @@ -1,13 +1,22 @@ #ifndef CLP_CLP_FILEDECOMPRESSOR_HPP #define CLP_CLP_FILEDECOMPRESSOR_HPP +#include +#include +#include #include #include "../FileWriter.hpp" +#include "../ir/constants.hpp" +#include "../ir/LogEventSerializer.hpp" +#include "../spdlog_with_specializations.hpp" #include "../streaming_archive/MetadataDB.hpp" #include "../streaming_archive/reader/Archive.hpp" #include "../streaming_archive/reader/File.hpp" #include "../streaming_archive/reader/Message.hpp" +#include "ErrorCode.hpp" +#include "ir/types.hpp" +#include "Utils.hpp" namespace clp::clp { /** @@ -24,6 +33,30 @@ class FileDecompressor { std::unordered_map& temp_path_to_final_path ); + /** + * Decompresses the given file split into one or more IR files (chunks). The function creates a + * new IR chunk when the current IR chunk exceeds ir_target_size. + * + * @tparam IrOutputHandler Function to handle the resulting IR chunks. + * Signature: (std::filesystem::path const& ir_file_path, string const& orig_file_id, + * size_t begin_message_ix, size_t end_message_ix, bool is_last_ir_chunk) -> bool; + * The function returns whether it succeeded. + * @param archive_reader + * @param file_metadata_ix + * @param ir_target_size Target size of each IR chunk. NOTE: This is not a hard limit. + * @param output_dir Directory to write IR chunks to + * @param ir_output_handler + * @return Whether decompression was successful. + */ + template + auto decompress_to_ir( + streaming_archive::reader::Archive& archive_reader, + streaming_archive::MetadataDB::FileIterator const& file_metadata_ix, + size_t ir_target_size, + std::string const& output_dir, + IrOutputHandler ir_output_handler + ) -> bool; + private: // Variables FileWriter m_decompressed_file_writer; @@ -31,6 +64,113 @@ class FileDecompressor { streaming_archive::reader::Message m_encoded_message; std::string m_decompressed_message; }; + +// Templated methods +template +auto FileDecompressor::decompress_to_ir( + streaming_archive::reader::Archive& archive_reader, + streaming_archive::MetadataDB::FileIterator const& file_metadata_ix, + size_t ir_target_size, + std::string const& output_dir, + IrOutputHandler ir_output_handler +) -> bool { + // Open encoded file + if (auto const error_code = archive_reader.open_file(m_encoded_file, file_metadata_ix); + ErrorCode_Success != error_code) + { + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR("Failed to open encoded file, errno={}", errno); + } else { + SPDLOG_ERROR("Failed to open encoded file, error_code={}", error_code); + } + return false; + } + + // Generate output directory + if (auto const error_code = create_directory_structure(output_dir, 0700); + ErrorCode_Success != error_code) + { + SPDLOG_ERROR( + "Failed to create directory structure {}, errno={}", + output_dir.c_str(), + errno + ); + return false; + } + + std::filesystem::path ir_output_path{output_dir}; + auto ir_file_name = m_encoded_file.get_id_as_string(); + ir_file_name += ir::cIrFileExtension; + ir_output_path /= ir_file_name; + + auto const& file_orig_id = m_encoded_file.get_orig_file_id_as_string(); + auto begin_message_ix = m_encoded_file.get_begin_message_ix(); + + ir::LogEventSerializer ir_serializer; + // Open output IR file + if (false == ir_serializer.open(ir_output_path.string())) { + SPDLOG_ERROR("Failed to serialize preamble"); + return false; + } + + while (archive_reader.get_next_message(m_encoded_file, m_encoded_message)) { + if (false + == archive_reader + .decompress_message_without_ts(m_encoded_message, m_decompressed_message)) + { + SPDLOG_ERROR("Failed to decompress message"); + return false; + } + + if (ir_serializer.get_serialized_size() >= ir_target_size) { + ir_serializer.close(); + + auto const end_message_ix = begin_message_ix + ir_serializer.get_num_log_events(); + if (false + == ir_output_handler( + ir_output_path, + file_orig_id, + begin_message_ix, + end_message_ix, + false + )) + { + return false; + } + begin_message_ix = end_message_ix; + + if (false == ir_serializer.open(ir_output_path.string())) { + SPDLOG_ERROR("Failed to serialize preamble"); + return false; + } + } + + if (false + == ir_serializer.serialize_log_event( + m_encoded_message.get_ts_in_milli(), + m_decompressed_message + )) + { + SPDLOG_ERROR( + "Failed to serialize log event: {} with ts {}", + m_decompressed_message.c_str(), + m_encoded_message.get_ts_in_milli() + ); + return false; + } + } + auto const end_message_ix = begin_message_ix + ir_serializer.get_num_log_events(); + ir_serializer.close(); + + if (false + == ir_output_handler(ir_output_path, file_orig_id, begin_message_ix, end_message_ix, true)) + { + return false; + } + + archive_reader.close_file(m_encoded_file); + return true; +} }; // namespace clp::clp #endif // CLP_CLP_FILEDECOMPRESSOR_HPP diff --git a/components/core/src/clp/clp/decompression.cpp b/components/core/src/clp/clp/decompression.cpp index c3340e570..6b87f6777 100644 --- a/components/core/src/clp/clp/decompression.cpp +++ b/components/core/src/clp/clp/decompression.cpp @@ -1,10 +1,8 @@ #include "decompression.hpp" +#include #include -#include -#include - #include "../ErrorCode.hpp" #include "../FileWriter.hpp" #include "../GlobalMySQLMetadataDB.hpp" @@ -14,6 +12,8 @@ #include "../TraceableException.hpp" #include "../Utils.hpp" #include "FileDecompressor.hpp" +#include "ir/constants.hpp" +#include "utils.hpp" using std::cerr; using std::make_unique; @@ -29,7 +29,7 @@ bool decompress( ErrorCode error_code; // Create output directory in case it doesn't exist - auto output_dir = boost::filesystem::path(command_line_args.get_output_dir()); + auto output_dir = std::filesystem::path(command_line_args.get_output_dir()); error_code = create_directory(output_dir.parent_path().string(), 0700, true); if (ErrorCode_Success != error_code) { SPDLOG_ERROR("Failed to create {} - {}", output_dir.parent_path().c_str(), strerror(errno)); @@ -39,33 +39,13 @@ bool decompress( unordered_set decompressed_files; try { - auto archives_dir = boost::filesystem::path(command_line_args.get_archives_dir()); + auto archives_dir = std::filesystem::path(command_line_args.get_archives_dir()); auto const& global_metadata_db_config = command_line_args.get_metadata_db_config(); - std::unique_ptr global_metadata_db; - switch (global_metadata_db_config.get_metadata_db_type()) { - case GlobalMetadataDBConfig::MetadataDBType::SQLite: { - auto global_metadata_db_path - = archives_dir / streaming_archive::cMetadataDBFileName; - global_metadata_db - = std::make_unique(global_metadata_db_path.string() - ); - break; - } - case GlobalMetadataDBConfig::MetadataDBType::MySQL: - global_metadata_db = std::make_unique( - global_metadata_db_config.get_metadata_db_host(), - global_metadata_db_config.get_metadata_db_port(), - global_metadata_db_config.get_metadata_db_username(), - global_metadata_db_config.get_metadata_db_password(), - global_metadata_db_config.get_metadata_db_name(), - global_metadata_db_config.get_metadata_table_prefix() - ); - break; - } + auto global_metadata_db = get_global_metadata_db(global_metadata_db_config, archives_dir); streaming_archive::reader::Archive archive_reader; - boost::filesystem::path empty_directory_path; + std::filesystem::path empty_directory_path; FileDecompressor file_decompressor; @@ -83,7 +63,7 @@ bool decompress( archive_ix->get_id(archive_id); auto archive_path = archives_dir / archive_id; - if (false == boost::filesystem::exists(archive_path)) { + if (false == std::filesystem::exists(archive_path)) { SPDLOG_WARN( "Archive {} does not exist in '{}'.", archive_id, @@ -199,11 +179,11 @@ bool decompress( global_metadata_db->close(); string final_path; - boost::system::error_code boost_error_code; + std::error_code std_error_code; for (auto const& temp_path_and_final_path : temp_path_to_final_path) { final_path = temp_path_and_final_path.second; for (size_t i = 1; i < SIZE_MAX; ++i) { - if (boost::filesystem::exists(final_path, boost_error_code)) { + if (std::filesystem::exists(final_path, std_error_code)) { final_path = temp_path_and_final_path.second; final_path += '.'; final_path += std::to_string(i); @@ -251,4 +231,118 @@ bool decompress( return true; } + +bool decompress_to_ir(CommandLineArguments& command_line_args) { + ErrorCode error_code{}; + + // Create output directory in case it doesn't exist + std::filesystem::path output_dir{command_line_args.get_output_dir()}; + error_code = create_directory(output_dir.parent_path().string(), 0700, true); + if (ErrorCode_Success != error_code) { + SPDLOG_ERROR("Failed to create {} - {}", output_dir.parent_path().c_str(), strerror(errno)); + return false; + } + + try { + std::filesystem::path archives_dir{command_line_args.get_archives_dir()}; + auto const& global_metadata_db_config = command_line_args.get_metadata_db_config(); + auto global_metadata_db = get_global_metadata_db(global_metadata_db_config, archives_dir); + + global_metadata_db->open(); + string archive_id; + string file_split_id; + if (false + == global_metadata_db->get_file_split( + command_line_args.get_orig_file_id(), + command_line_args.get_ir_msg_ix(), + archive_id, + file_split_id + )) + { + SPDLOG_ERROR( + "Failed to find file split containing msg_ix {}", + command_line_args.get_ir_msg_ix() + ); + return false; + } + global_metadata_db->close(); + + streaming_archive::reader::Archive archive_reader; + auto archive_path = archives_dir / archive_id; + archive_reader.open(archive_path.string()); + archive_reader.refresh_dictionaries(); + + auto file_metadata_ix_ptr = archive_reader.get_file_iterator_by_split_id(file_split_id); + if (false == file_metadata_ix_ptr->has_next()) { + SPDLOG_ERROR("File split doesn't exist {} in archive {}", file_split_id, archive_id); + return false; + } + + auto ir_output_handler = [&](std::filesystem::path const& src_ir_path, + string const& orig_file_id, + size_t begin_message_ix, + size_t end_message_ix, + [[maybe_unused]] bool is_last_ir_chunk) { + auto dest_ir_file_name = orig_file_id; + dest_ir_file_name += "_" + std::to_string(begin_message_ix); + dest_ir_file_name += "_" + std::to_string(end_message_ix); + dest_ir_file_name += ir::cIrFileExtension; + + auto const dest_ir_path = output_dir / dest_ir_file_name; + try { + std::filesystem::rename(src_ir_path, dest_ir_path); + } catch (std::filesystem::filesystem_error const& e) { + SPDLOG_ERROR( + "Failed to rename from {} to {}. Error: {}", + src_ir_path.c_str(), + dest_ir_path.c_str(), + e.what() + ); + return false; + } + return true; + }; + + // Decompress file split + FileDecompressor file_decompressor; + if (false + == file_decompressor.decompress_to_ir( + archive_reader, + *file_metadata_ix_ptr, + command_line_args.get_ir_target_size(), + command_line_args.get_ir_temp_output_dir(), + ir_output_handler + )) + { + return false; + } + + file_metadata_ix_ptr.reset(nullptr); + + archive_reader.close(); + } catch (TraceableException& e) { + error_code = e.get_error_code(); + if (ErrorCode_errno == error_code) { + SPDLOG_ERROR( + "Decompression failed: {}:{} {}, errno={}", + e.get_filename(), + e.get_line_number(), + e.what(), + errno + ); + return false; + } else { + SPDLOG_ERROR( + "Decompression failed: {}:{} {}, error_code={}", + e.get_filename(), + e.get_line_number(), + e.what(), + error_code + ); + return false; + } + } + + return true; +} } // namespace clp::clp diff --git a/components/core/src/clp/clp/decompression.hpp b/components/core/src/clp/clp/decompression.hpp index 60c5270ec..c5043ae2e 100644 --- a/components/core/src/clp/clp/decompression.hpp +++ b/components/core/src/clp/clp/decompression.hpp @@ -17,6 +17,12 @@ bool decompress( CommandLineArguments& command_line_args, std::unordered_set const& files_to_decompress ); +/** + * Decompresses a file split from an archive into one or more IR chunks in the the given directory. + * @param command_line_args + * @return Whether decompression was successful. + */ +bool decompress_to_ir(CommandLineArguments& command_line_args); } // namespace clp::clp #endif // CLP_CLP_DECOMPRESSION_HPP diff --git a/components/core/src/clp/clp/run.cpp b/components/core/src/clp/clp/run.cpp index 1eb9e2f8a..5a3b0eb27 100644 --- a/components/core/src/clp/clp/run.cpp +++ b/components/core/src/clp/clp/run.cpp @@ -54,7 +54,8 @@ int run(int argc, char const* argv[]) { } } - if (CommandLineArguments::Command::Compress == command_line_args.get_command()) { + auto command = command_line_args.get_command(); + if (CommandLineArguments::Command::Compress == command) { /// TODO: make this not a unique_ptr and test performance difference std::unique_ptr reader_parser; if (!command_line_args.get_use_heuristic()) { @@ -134,11 +135,18 @@ int run(int argc, char const* argv[]) { if (!compression_successful) { return -1; } - } else { // CommandLineArguments::Command::Extract == command + } else if (CommandLineArguments::Command::Extract == command) { unordered_set files_to_decompress(input_paths.cbegin(), input_paths.cend()); - if (!decompress(command_line_args, files_to_decompress)) { + if (false == decompress(command_line_args, files_to_decompress)) { return -1; } + } else if (CommandLineArguments::Command::ExtractIr == command) { + if (false == decompress_to_ir(command_line_args)) { + return -1; + } + } else { + SPDLOG_ERROR("Command {} not implemented.", enum_to_underlying_type(command)); + return -1; } Profiler::stop_continuous_measurement(); diff --git a/components/core/src/clp/clp/utils.cpp b/components/core/src/clp/clp/utils.cpp index 715eee858..0f05d75ac 100644 --- a/components/core/src/clp/clp/utils.cpp +++ b/components/core/src/clp/clp/utils.cpp @@ -1,12 +1,17 @@ #include "utils.hpp" -#include +#include +#include #include #include "../ErrorCode.hpp" +#include "../GlobalMySQLMetadataDB.hpp" +#include "../GlobalSQLiteMetadataDB.hpp" #include "../spdlog_with_specializations.hpp" #include "../Utils.hpp" +#include "streaming_archive/Constants.hpp" +#include "TraceableException.hpp" using std::string; using std::vector; @@ -81,40 +86,6 @@ bool find_all_files_and_empty_directories( return true; } -bool is_utf8_sequence(size_t sequence_length, char const* sequence) { - size_t num_utf8_bytes_to_read = 0; - for (size_t i = 0; i < sequence_length; ++i) { - auto byte = sequence[i]; - - if (num_utf8_bytes_to_read > 0) { - // Validate that byte matches 0b10xx_xxxx - if ((byte & 0xC0) != 0x80) { - return false; - } - --num_utf8_bytes_to_read; - } else { - if (byte & 0x80) { - // Check if byte is valid UTF-8 length-indicator - if ((byte & 0xF8) == 0xF0) { - // Matches 0b1111_0xxx - num_utf8_bytes_to_read = 3; - } else if ((byte & 0xF0) == 0xE0) { - // Matches 0b1110_xxxx - num_utf8_bytes_to_read = 2; - } else if ((byte & 0xE0) == 0xC0) { - // Matches 0b110x_xxxx - num_utf8_bytes_to_read = 1; - } else { - // Invalid UTF-8 length-indicator - return false; - } - } // else byte is ASCII - } - } - - return true; -} - bool read_input_paths(string const& list_path, vector& paths) { ErrorCode error_code = read_list_of_paths(list_path, paths); if (ErrorCode_Success != error_code) { @@ -200,4 +171,29 @@ bool validate_paths_exist(vector const& paths) { return all_paths_exist; } + +std::unique_ptr get_global_metadata_db( + GlobalMetadataDBConfig const& global_metadata_db_config, + std::filesystem::path const& archives_dir +) { + switch (global_metadata_db_config.get_metadata_db_type()) { + case GlobalMetadataDBConfig::MetadataDBType::SQLite: { + auto global_metadata_db_path + = archives_dir + / static_cast(streaming_archive::cMetadataDBFileName); + return std::make_unique(global_metadata_db_path.string()); + } + case GlobalMetadataDBConfig::MetadataDBType::MySQL: + return std::make_unique( + global_metadata_db_config.get_metadata_db_host(), + global_metadata_db_config.get_metadata_db_port(), + global_metadata_db_config.get_metadata_db_username(), + global_metadata_db_config.get_metadata_db_password(), + global_metadata_db_config.get_metadata_db_name(), + global_metadata_db_config.get_metadata_table_prefix() + ); + default: + throw ClpOperationFailed(ErrorCode_Unsupported, __FILENAME__, __LINE__); + } +} } // namespace clp::clp diff --git a/components/core/src/clp/clp/utils.hpp b/components/core/src/clp/clp/utils.hpp index a53277572..0a6918445 100644 --- a/components/core/src/clp/clp/utils.hpp +++ b/components/core/src/clp/clp/utils.hpp @@ -1,13 +1,31 @@ #ifndef CLP_CLP_UTILS_HPP #define CLP_CLP_UTILS_HPP +#include +#include #include #include +#include "../GlobalMetadataDB.hpp" +#include "../GlobalMetadataDBConfig.hpp" +#include "ErrorCode.hpp" #include "FileToCompress.hpp" +#include "TraceableException.hpp" namespace clp::clp { +// Types +class ClpOperationFailed : public TraceableException { +public: + // Constructors + ClpOperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : TraceableException(error_code, filename, line_number) {} + + // Methods + [[nodiscard]] char const* what() const noexcept override { return "CLP operation failed"; } +}; + +// Methods /** * Recursively finds all files and empty directories at the given path * @param path_prefix_to_remove @@ -23,14 +41,6 @@ bool find_all_files_and_empty_directories( std::vector& empty_directory_paths ); -/** - * Checks if the given sequence is valid UTF-8 - * @param sequence_length - * @param sequence - * @return true if valid, false otherwise - */ -bool is_utf8_sequence(size_t sequence_length, char const* sequence); - /** * Reads a list of input paths * @param list_path @@ -61,6 +71,17 @@ bool remove_prefix_and_clean_up_path( * @return true if they all exist, false otherwise */ bool validate_paths_exist(std::vector const& paths); + +/** + * Chooses and initializes the relevant global metadata DB class based on the given config. + * @param global_metadata_db_config + * @param archives_dir + * @return The relevant global metadata DB class. + */ +std::unique_ptr get_global_metadata_db( + GlobalMetadataDBConfig const& global_metadata_db_config, + std::filesystem::path const& archives_dir +); } // namespace clp::clp #endif // CLP_CLP_UTILS_HPP diff --git a/components/core/src/clp/ffi/ir_stream/Serializer.cpp b/components/core/src/clp/ffi/ir_stream/Serializer.cpp new file mode 100644 index 000000000..5a9325553 --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/Serializer.cpp @@ -0,0 +1,532 @@ +#include "Serializer.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../../ir/types.hpp" +#include "../../time_types.hpp" +#include "../../type_utils.hpp" +#include "../encoding_methods.hpp" +#include "../SchemaTree.hpp" +#include "../SchemaTreeNode.hpp" +#include "encoding_methods.hpp" +#include "protocol_constants.hpp" +#include "utils.hpp" + +using std::optional; +using std::span; +using std::string; +using std::string_view; +using std::vector; + +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::four_byte_encoded_variable_t; + +namespace clp::ffi::ir_stream { +namespace { +/** + * Class for iterating the kv-pairs of a MessagePack map. + */ +class MsgpackMapIterator { +public: + // Types + using Child = msgpack::object_kv; + + // Constructors + MsgpackMapIterator(SchemaTreeNode::id_t schema_tree_node_id, span children) + : m_schema_tree_node_id{schema_tree_node_id}, + m_children{children}, + m_curr_child_it{m_children.begin()} {} + + // Methods + /** + * @return This map's ID in the schema tree. + */ + [[nodiscard]] auto get_schema_tree_node_id() const -> SchemaTreeNode::id_t { + return m_schema_tree_node_id; + } + + /** + * @return Whether there are more children to traverse. + */ + [[nodiscard]] auto has_next_child() const -> bool { + return m_curr_child_it != m_children.end(); + } + + /** + * Gets the next child and advances the underlying child idx. + * @return The next child to traverse. + */ + [[nodiscard]] auto get_next_child() -> Child const& { return *(m_curr_child_it++); } + +private: + SchemaTreeNode::id_t m_schema_tree_node_id; + span m_children; + span::iterator m_curr_child_it; +}; + +/** + * Gets the schema-tree node type that corresponds with a given MessagePack value. + * @param val + * @return The corresponding schema-tree node type. + * @return std::nullopt if the value doesn't match any of the supported schema-tree node types. + */ +[[nodiscard]] auto get_schema_tree_node_type_from_msgpack_val(msgpack::object const& val +) -> optional; + +/** + * Serializes an empty object. + * @param output_buf + */ +auto serialize_value_empty_object(vector& output_buf) -> void; + +/** + * Serializes an integer. + * @param val + * @param output_buf + * @return Whether serialization succeeded. + */ +auto serialize_value_int(int64_t val, vector& output_buf) -> void; + +/** + * Serializes a float. + * @param val + * @param output_buf + */ +auto serialize_value_float(double val, vector& output_buf) -> void; + +/** + * Serializes a boolean. + * @param val + * @param output_buf + */ +auto serialize_value_bool(bool val, vector& output_buf) -> void; + +/** + * Serializes a null. + * @param output_buf + */ +auto serialize_value_null(vector& output_buf) -> void; + +/** + * Serializes a string. + * @tparam encoded_variable_t + * @param val + * @param logtype_buf + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto +serialize_value_string(string_view val, string& logtype_buf, vector& output_buf) -> bool; + +/** + * Serializes a MessagePack array as a JSON string, using CLP's encoding for unstructured text. + * @tparam encoded_variable_t + * @param val + * @param logtype_buf + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto serialize_value_array( + msgpack::object const& val, + string& logtype_buf, + vector& output_buf +) -> bool; + +auto get_schema_tree_node_type_from_msgpack_val(msgpack::object const& val +) -> optional { + optional ret_val; + switch (val.type) { + case msgpack::type::POSITIVE_INTEGER: + case msgpack::type::NEGATIVE_INTEGER: + ret_val.emplace(SchemaTreeNode::Type::Int); + break; + case msgpack::type::FLOAT32: + case msgpack::type::FLOAT64: + ret_val.emplace(SchemaTreeNode::Type::Float); + break; + case msgpack::type::STR: + ret_val.emplace(SchemaTreeNode::Type::Str); + break; + case msgpack::type::BOOLEAN: + ret_val.emplace(SchemaTreeNode::Type::Bool); + break; + case msgpack::type::NIL: + case msgpack::type::MAP: + ret_val.emplace(SchemaTreeNode::Type::Obj); + break; + case msgpack::type::ARRAY: + ret_val.emplace(SchemaTreeNode::Type::UnstructuredArray); + break; + default: + return std::nullopt; + } + return ret_val; +} + +auto serialize_value_empty_object(vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueEmpty); +} + +auto serialize_value_int(int64_t val, vector& output_buf) -> void { + if (INT8_MIN <= val && val <= INT8_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt8); + output_buf.push_back(static_cast(val)); + } else if (INT16_MIN <= val && val <= INT16_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt16); + serialize_int(static_cast(val), output_buf); + } else if (INT32_MIN <= val && val <= INT32_MAX) { + output_buf.push_back(cProtocol::Payload::ValueInt32); + serialize_int(static_cast(val), output_buf); + } else { // (INT64_MIN <= val && val <= INT64_MAX) + output_buf.push_back(cProtocol::Payload::ValueInt64); + serialize_int(val, output_buf); + } +} + +auto serialize_value_float(double val, vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueFloat); + serialize_int(bit_cast(val), output_buf); +} + +auto serialize_value_bool(bool val, vector& output_buf) -> void { + output_buf.push_back(val ? cProtocol::Payload::ValueTrue : cProtocol::Payload::ValueFalse); +} + +auto serialize_value_null(vector& output_buf) -> void { + output_buf.push_back(cProtocol::Payload::ValueNull); +} + +template +auto serialize_value_string(string_view val, string& logtype_buf, vector& output_buf) + -> bool { + if (string_view::npos == val.find(' ')) { + return serialize_string(val, output_buf); + } + logtype_buf.clear(); + return serialize_clp_string(val, logtype_buf, output_buf); +} + +template +auto serialize_value_array( + msgpack::object const& val, + string& logtype_buf, + vector& output_buf +) -> bool { + std::ostringstream oss; + oss << val; + logtype_buf.clear(); + return serialize_clp_string(oss.str(), logtype_buf, output_buf); +} +} // namespace + +template +auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result> { + static_assert( + (std::is_same_v + || std::is_same_v) + ); + + Serializer serializer; + auto& ir_buf{serializer.m_ir_buf}; + constexpr BufferView cMagicNumber{ + static_cast( + std::is_same_v + ? cProtocol::EightByteEncodingMagicNumber + : cProtocol::FourByteEncodingMagicNumber + ), + cProtocol::MagicNumberLength + }; + ir_buf.insert(ir_buf.cend(), cMagicNumber.begin(), cMagicNumber.end()); + + nlohmann::json metadata; + metadata.emplace(cProtocol::Metadata::VersionKey, cProtocol::Metadata::BetaVersionValue); + metadata.emplace(cProtocol::Metadata::VariablesSchemaIdKey, cVariablesSchemaVersion); + metadata.emplace( + cProtocol::Metadata::VariableEncodingMethodsIdKey, + cVariableEncodingMethodsVersion + ); + if (false == serialize_metadata(metadata, ir_buf)) { + return std::errc::protocol_error; + } + + return serializer; +} + +template +auto Serializer::change_utc_offset(UtcOffset utc_offset) -> void { + if (utc_offset != m_curr_utc_offset) { + m_curr_utc_offset = utc_offset; + } + serialize_utc_offset_change(m_curr_utc_offset, m_ir_buf); +} + +template +auto Serializer::serialize_msgpack_map(msgpack::object_map const& msgpack_map +) -> bool { + if (0 == msgpack_map.size) { + serialize_value_empty_object(m_ir_buf); + return true; + } + + m_schema_tree.take_snapshot(); + m_schema_tree_node_buf.clear(); + m_key_group_buf.clear(); + m_value_group_buf.clear(); + + // Traverse the map using DFS iteratively + bool failure{false}; + vector dfs_stack; + dfs_stack.emplace_back( + SchemaTree::cRootId, + span{msgpack_map.ptr, msgpack_map.size} + ); + while (false == dfs_stack.empty()) { + auto& curr{dfs_stack.back()}; + if (false == curr.has_next_child()) { + // Visited all children, so pop node + dfs_stack.pop_back(); + continue; + } + + // Convert the current value's type to its corresponding schema-tree node type + auto const& [key, val]{curr.get_next_child()}; + auto const opt_schema_tree_node_type{get_schema_tree_node_type_from_msgpack_val(val)}; + if (false == opt_schema_tree_node_type.has_value()) { + failure = true; + break; + } + auto const schema_tree_node_type{opt_schema_tree_node_type.value()}; + + SchemaTree::NodeLocator const locator{ + curr.get_schema_tree_node_id(), + key.as(), + schema_tree_node_type + }; + + // Get the schema-tree node that corresponds with the current kv-pair, or add it if it + // doesn't exist. + auto opt_schema_tree_node_id{m_schema_tree.try_get_node_id(locator)}; + if (false == opt_schema_tree_node_id.has_value()) { + opt_schema_tree_node_id.emplace(m_schema_tree.insert_node(locator)); + if (false == serialize_schema_tree_node(locator)) { + failure = true; + break; + } + } + auto const schema_tree_node_id{opt_schema_tree_node_id.value()}; + + if (msgpack::type::MAP == val.type) { + // Serialize map + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + auto const& inner_map{val.via.map}; + auto const inner_map_size(inner_map.size); + if (0 == inner_map_size) { + // Value is an empty map, so we can serialize it immediately + if (false == serialize_key(schema_tree_node_id)) { + failure = true; + break; + } + serialize_value_empty_object(m_value_group_buf); + } else { + // Add map for DFS iteration + dfs_stack.emplace_back( + schema_tree_node_id, + span{inner_map.ptr, inner_map_size} + ); + } + } else { + // Serialize primitive + if (false + == (serialize_key(schema_tree_node_id) && serialize_val(val, schema_tree_node_type) + )) + { + failure = true; + break; + } + } + } + + if (failure) { + m_schema_tree.revert(); + return false; + } + + m_ir_buf.insert( + m_ir_buf.cend(), + m_schema_tree_node_buf.cbegin(), + m_schema_tree_node_buf.cend() + ); + m_ir_buf.insert(m_ir_buf.cend(), m_key_group_buf.cbegin(), m_key_group_buf.cend()); + m_ir_buf.insert(m_ir_buf.cend(), m_value_group_buf.cbegin(), m_value_group_buf.cend()); + return true; +} + +template +auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool { + switch (locator.get_type()) { + case SchemaTreeNode::Type::Int: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeInt); + break; + case SchemaTreeNode::Type::Float: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeFloat); + break; + case SchemaTreeNode::Type::Bool: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeBool); + break; + case SchemaTreeNode::Type::Str: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeStr); + break; + case SchemaTreeNode::Type::UnstructuredArray: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeUnstructuredArray); + break; + case SchemaTreeNode::Type::Obj: + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeObj); + break; + default: + // Unknown type + return false; + } + + auto const parent_id{locator.get_parent_id()}; + if (parent_id <= UINT8_MAX) { + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeParentIdUByte); + m_schema_tree_node_buf.push_back(bit_cast(static_cast(parent_id))); + } else if (parent_id <= UINT16_MAX) { + m_schema_tree_node_buf.push_back(cProtocol::Payload::SchemaTreeNodeParentIdUShort); + serialize_int(static_cast(parent_id), m_schema_tree_node_buf); + } else { + // Out of range + return false; + } + + return serialize_string(locator.get_key_name(), m_schema_tree_node_buf); +} + +template +auto Serializer::serialize_key(SchemaTreeNode::id_t id) -> bool { + if (id <= UINT8_MAX) { + m_key_group_buf.push_back(cProtocol::Payload::KeyIdUByte); + m_key_group_buf.push_back(bit_cast(static_cast(id))); + } else if (id <= UINT16_MAX) { + m_key_group_buf.push_back(cProtocol::Payload::KeyIdUShort); + serialize_int(static_cast(id), m_key_group_buf); + } else { + return false; + } + return true; +} + +template +auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTreeNode::Type schema_tree_node_type +) -> bool { + switch (schema_tree_node_type) { + case SchemaTreeNode::Type::Int: + if (msgpack::type::POSITIVE_INTEGER == val.type + && static_cast(INT64_MAX) < val.as()) + { + return false; + } + serialize_value_int(val.as(), m_value_group_buf); + break; + + case SchemaTreeNode::Type::Float: + serialize_value_float(val.as(), m_value_group_buf); + break; + + case SchemaTreeNode::Type::Bool: + serialize_value_bool(val.as(), m_value_group_buf); + break; + + case SchemaTreeNode::Type::Str: + if (false + == serialize_value_string( + val.as(), + m_logtype_buf, + m_value_group_buf + )) + { + return false; + } + break; + + case SchemaTreeNode::Type::Obj: + if (msgpack::type::NIL != val.type) { + return false; + } + serialize_value_null(m_value_group_buf); + break; + + case SchemaTreeNode::Type::UnstructuredArray: + if (false + == serialize_value_array(val, m_logtype_buf, m_value_group_buf)) + { + return false; + } + break; + + default: + // Unknown schema tree node type + return false; + } + return true; +} + +// Explicitly declare template specializations so that we can define the template methods in this +// file +template auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result>; +template auto Serializer::create( +) -> OUTCOME_V2_NAMESPACE::std_result>; + +template auto Serializer::change_utc_offset(UtcOffset utc_offset +) -> void; +template auto Serializer::change_utc_offset(UtcOffset utc_offset +) -> void; + +template auto Serializer::serialize_msgpack_map( + msgpack::object_map const& msgpack_map +) -> bool; +template auto Serializer::serialize_msgpack_map( + msgpack::object_map const& msgpack_map +) -> bool; + +template auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool; +template auto Serializer::serialize_schema_tree_node( + SchemaTree::NodeLocator const& locator +) -> bool; + +template auto Serializer::serialize_key(SchemaTreeNode::id_t id +) -> bool; +template auto Serializer::serialize_key(SchemaTreeNode::id_t id +) -> bool; + +template auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTreeNode::Type schema_tree_node_type +) -> bool; +template auto Serializer::serialize_val( + msgpack::object const& val, + SchemaTreeNode::Type schema_tree_node_type +) -> bool; +} // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/Serializer.hpp b/components/core/src/clp/ffi/ir_stream/Serializer.hpp new file mode 100644 index 000000000..950d8481f --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/Serializer.hpp @@ -0,0 +1,132 @@ +#ifndef CLP_FFI_IR_STREAM_SERIALIZER_HPP +#define CLP_FFI_IR_STREAM_SERIALIZER_HPP + +#include +#include +#include +#include + +#include +#include + +#include "../../time_types.hpp" +#include "../SchemaTree.hpp" +#include "../SchemaTreeNode.hpp" + +namespace clp::ffi::ir_stream { +/** + * A work-in-progress class for serializing log events into the kv-pair IR format. + * + * This class: + * - maintains all necessary internal data structures to track serialization state; + * - provides APIs to serialize log events into the IR format; and + * - provides APIs to access the serialized IR bytes. + * + * NOTE: + * - This class is designed only to provide serialization functionalities. Callers are responsible + * for writing the serialized bytes into I/O streams. + * - This class doesn't provide an API to terminate the IR stream. Callers should + * terminate the stream by flushing this class' IR buffer to the I/O stream and then writing + * `clp::ffi::ir_stream::cProtocol::Eof` to the I/O stream. + * @tparam encoded_variable_t Type of encoded variables in the serialized IR stream. + */ +template +class Serializer { +public: + // Types + using Buffer = std::vector; + using BufferView = std::span; + + // Factory functions + /** + * Creates an IR serializer and serializes the stream's preamble. + * @return A result containing the serializer or an error code indicating the failure: + * - std::errc::protocol_error on failure to serialize the preamble. + */ + [[nodiscard]] static auto create( + ) -> OUTCOME_V2_NAMESPACE::std_result>; + + // Disable copy constructor/assignment operator + Serializer(Serializer const&) = delete; + auto operator=(Serializer const&) -> Serializer& = delete; + + // Define default move constructor/assignment operator + Serializer(Serializer&&) = default; + auto operator=(Serializer&&) -> Serializer& = default; + + // Destructor + ~Serializer() = default; + + // Methods + /** + * @return A view of the underlying IR buffer which contains the serialized IR bytes. + */ + [[nodiscard]] auto get_ir_buf_view() const -> BufferView { + return {m_ir_buf.data(), m_ir_buf.size()}; + } + + /** + * Clears the underlying IR buffer. + */ + auto clear_ir_buf() -> void { m_ir_buf.clear(); } + + /** + * @return The current UTC offset. + */ + [[nodiscard]] auto get_curr_utc_offset() const -> UtcOffset { return m_curr_utc_offset; } + + /** + * Changes the UTC offset and serializes a UTC offset change packet, if the given UTC offset is + * different than the current UTC offset. + * @param utc_offset + */ + auto change_utc_offset(UtcOffset utc_offset) -> void; + + /** + * Serializes the given msgpack map as a key-value pair log event. + * @param msgpack_map + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto serialize_msgpack_map(msgpack::object_map const& msgpack_map) -> bool; + +private: + // Constructors + Serializer() = default; + + // Methods + /** + * Serializes a schema tree node identified by the given locator into `m_schema_tree_node_buf`. + * @param locator + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto serialize_schema_tree_node(SchemaTree::NodeLocator const& locator) -> bool; + + /** + * Serializes the given key ID into `m_key_group_buf`. + * @param id + * @return true on success. + * @return false if the ID exceeds the representable range. + */ + [[nodiscard]] auto serialize_key(SchemaTreeNode::id_t id) -> bool; + + /** + * Serializes the given MessagePack value into `m_value_group_buf`. + * @param val + * @param schema_tree_node_type The type of the schema tree node that corresponds to `val`. + * @return Whether serialization succeeded. + */ + [[nodiscard]] auto + serialize_val(msgpack::object const& val, SchemaTreeNode::Type schema_tree_node_type) -> bool; + + UtcOffset m_curr_utc_offset{0}; + Buffer m_ir_buf; + SchemaTree m_schema_tree; + + std::string m_logtype_buf; + Buffer m_schema_tree_node_buf; + Buffer m_key_group_buf; + Buffer m_value_group_buf; +}; +} // namespace clp::ffi::ir_stream + +#endif diff --git a/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp b/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp index 682972e9d..128d659c1 100644 --- a/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp +++ b/components/core/src/clp/ffi/ir_stream/encoding_methods.cpp @@ -5,8 +5,8 @@ #include "../../ir/parsing.hpp" #include "../../ir/types.hpp" #include "../../time_types.hpp" -#include "byteswap.hpp" #include "protocol_constants.hpp" +#include "utils.hpp" using clp::ir::eight_byte_encoded_variable_t; using clp::ir::epoch_time_ms_t; @@ -17,15 +17,6 @@ using std::vector; namespace clp::ffi::ir_stream { // Local function prototypes -/** - * Serializes the given integer into the IR stream - * @tparam integer_t - * @param value - * @param ir_buf - */ -template -static void serialize_int(integer_t value, vector& ir_buf); - /** * Serializes the given logtype into the IR stream * @param logtype @@ -34,14 +25,6 @@ static void serialize_int(integer_t value, vector& ir_buf); */ static bool serialize_logtype(string_view logtype, vector& ir_buf); -/** - * Serializes the given metadata into the IR stream - * @param metadata - * @param ir_buf - * @return true on success, false otherwise - */ -static bool serialize_metadata(nlohmann::json& metadata, vector& ir_buf); - /** * Adds the basic metadata fields to the given JSON object * @param timestamp_pattern @@ -90,21 +73,6 @@ class DictionaryVariableHandler { vector& m_ir_buf; }; -template -static void serialize_int(integer_t value, vector& ir_buf) { - integer_t value_big_endian; - static_assert(sizeof(integer_t) == 2 || sizeof(integer_t) == 4 || sizeof(integer_t) == 8); - if constexpr (sizeof(value) == 2) { - value_big_endian = bswap_16(value); - } else if constexpr (sizeof(value) == 4) { - value_big_endian = bswap_32(value); - } else if constexpr (sizeof(value) == 8) { - value_big_endian = bswap_64(value); - } - auto data = reinterpret_cast(&value_big_endian); - ir_buf.insert(ir_buf.end(), data, data + sizeof(value)); -} - static bool serialize_logtype(string_view logtype, vector& ir_buf) { auto length = logtype.length(); if (length <= UINT8_MAX) { @@ -124,27 +92,6 @@ static bool serialize_logtype(string_view logtype, vector& ir_buf) { return true; } -static bool serialize_metadata(nlohmann::json& metadata, vector& ir_buf) { - ir_buf.push_back(cProtocol::Metadata::EncodingJson); - - auto metadata_serialized - = metadata.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore); - auto metadata_serialized_length = metadata_serialized.length(); - if (metadata_serialized_length <= UINT8_MAX) { - ir_buf.push_back(cProtocol::Metadata::LengthUByte); - ir_buf.push_back(bit_cast(static_cast(metadata_serialized_length))); - } else if (metadata_serialized_length <= UINT16_MAX) { - ir_buf.push_back(cProtocol::Metadata::LengthUShort); - serialize_int(static_cast(metadata_serialized_length), ir_buf); - } else { - // Can't encode metadata longer than 64 KiB - return false; - } - ir_buf.insert(ir_buf.cend(), metadata_serialized.cbegin(), metadata_serialized.cend()); - - return true; -} - static void add_base_metadata_fields( string_view timestamp_pattern, string_view timestamp_pattern_syntax, @@ -188,6 +135,22 @@ bool serialize_log_event( string_view message, string& logtype, vector& ir_buf +) { + if (false == serialize_message(message, logtype, ir_buf)) { + return false; + } + + // Encode timestamp + ir_buf.push_back(cProtocol::Payload::TimestampVal); + serialize_int(timestamp, ir_buf); + + return true; +} + +bool serialize_message( + std::string_view message, + std::string& logtype, + std::vector& ir_buf ) { auto encoded_var_handler = [&ir_buf](eight_byte_encoded_variable_t encoded_var) { ir_buf.push_back(cProtocol::Payload::VarEightByteEncoding); @@ -206,15 +169,7 @@ bool serialize_log_event( return false; } - if (false == serialize_logtype(logtype, ir_buf)) { - return false; - } - - // Encode timestamp - ir_buf.push_back(cProtocol::Payload::TimestampVal); - serialize_int(timestamp, ir_buf); - - return true; + return serialize_logtype(logtype, ir_buf); } } // namespace eight_byte_encoding diff --git a/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp b/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp index 0129e7550..be042e870 100644 --- a/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp +++ b/components/core/src/clp/ffi/ir_stream/encoding_methods.hpp @@ -39,6 +39,15 @@ bool serialize_log_event( std::string& logtype, std::vector& ir_buf ); + +/** + * Serializes the given message into the eight-byte encoding IR stream. + * @param message + * @param logtype Returns the message's logtype. + * @param ir_buf + * @return Whether the message was serialized successfully. + */ +bool serialize_message(std::string_view message, std::string& logtype, std::vector& ir_buf); } // namespace eight_byte_encoding namespace four_byte_encoding { diff --git a/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp b/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp index d442209de..9f84cc4e1 100644 --- a/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp +++ b/components/core/src/clp/ffi/ir_stream/protocol_constants.hpp @@ -13,6 +13,7 @@ constexpr int8_t LengthUShort = 0x12; constexpr char VersionKey[] = "VERSION"; constexpr char VersionValue[] = "0.0.2"; +constexpr char BetaVersionValue[] = "0.1.0-beta"; // The following regex can be used to validate a Semantic Versioning string. The source of the // regex can be found here: https://semver.org/ @@ -49,6 +50,35 @@ constexpr int8_t TimestampDeltaInt = 0x33; constexpr int8_t TimestampDeltaLong = 0x34; constexpr int8_t UtcOffsetChange = 0x3F; + +constexpr int8_t StrLenUByte = 0x41; +constexpr int8_t StrLenUShort = 0x42; +constexpr int8_t StrLenUInt = 0x43; + +constexpr int8_t ValueInt8 = 0x51; +constexpr int8_t ValueInt16 = 0x52; +constexpr int8_t ValueInt32 = 0x53; +constexpr int8_t ValueInt64 = 0x54; +constexpr int8_t ValueFloat = 0x56; +constexpr int8_t ValueTrue = 0x57; +constexpr int8_t ValueFalse = 0x58; +constexpr int8_t ValueFourByteEncodingClpStr = 0x59; +constexpr int8_t ValueEightByteEncodingClpStr = 0x5A; +constexpr int8_t ValueEmpty = 0x5E; +constexpr int8_t ValueNull = 0x5F; + +constexpr int8_t SchemaTreeNodeParentIdUByte = 0x60; +constexpr int8_t SchemaTreeNodeParentIdUShort = 0x61; + +constexpr int8_t KeyIdUByte = 0x65; +constexpr int8_t KeyIdUShort = 0x66; + +constexpr int8_t SchemaTreeNodeInt = 0x71; +constexpr int8_t SchemaTreeNodeFloat = 0x72; +constexpr int8_t SchemaTreeNodeBool = 0x73; +constexpr int8_t SchemaTreeNodeStr = 0x74; +constexpr int8_t SchemaTreeNodeUnstructuredArray = 0x75; +constexpr int8_t SchemaTreeNodeObj = 0x76; } // namespace Payload constexpr int8_t FourByteEncodingMagicNumber[] diff --git a/components/core/src/clp/ffi/ir_stream/utils.cpp b/components/core/src/clp/ffi/ir_stream/utils.cpp new file mode 100644 index 000000000..66277720c --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/utils.cpp @@ -0,0 +1,52 @@ +#include "utils.hpp" + +#include +#include +#include + +#include + +#include "../../type_utils.hpp" +#include "protocol_constants.hpp" + +namespace clp::ffi::ir_stream { +auto serialize_metadata(nlohmann::json& metadata, std::vector& output_buf) -> bool { + output_buf.push_back(cProtocol::Metadata::EncodingJson); + + auto const metadata_serialized + = metadata.dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore); + auto const metadata_serialized_length = metadata_serialized.length(); + if (metadata_serialized_length <= UINT8_MAX) { + output_buf.push_back(cProtocol::Metadata::LengthUByte); + output_buf.push_back(bit_cast(static_cast(metadata_serialized_length))); + } else if (metadata_serialized_length <= UINT16_MAX) { + output_buf.push_back(cProtocol::Metadata::LengthUShort); + serialize_int(static_cast(metadata_serialized_length), output_buf); + } else { + // Can't encode metadata longer than 64 KiB + return false; + } + output_buf.insert(output_buf.cend(), metadata_serialized.cbegin(), metadata_serialized.cend()); + + return true; +} + +auto serialize_string(std::string_view str, std::vector& output_buf) -> bool { + auto const length{str.length()}; + if (length <= UINT8_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUByte); + output_buf.push_back(bit_cast(static_cast(length))); + } else if (length <= UINT16_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUShort); + serialize_int(static_cast(length), output_buf); + } else if (length <= UINT32_MAX) { + output_buf.push_back(cProtocol::Payload::StrLenUInt); + serialize_int(static_cast(length), output_buf); + } else { + // Out of range + return false; + } + output_buf.insert(output_buf.cend(), str.cbegin(), str.cend()); + return true; +} +} // namespace clp::ffi::ir_stream diff --git a/components/core/src/clp/ffi/ir_stream/utils.hpp b/components/core/src/clp/ffi/ir_stream/utils.hpp new file mode 100644 index 000000000..7879c3f74 --- /dev/null +++ b/components/core/src/clp/ffi/ir_stream/utils.hpp @@ -0,0 +1,96 @@ +#ifndef CLP_FFI_IR_STREAM_UTILS_HPP +#define CLP_FFI_IR_STREAM_UTILS_HPP + +#include +#include +#include +#include +#include + +#include + +#include "../../ir/types.hpp" +#include "byteswap.hpp" +#include "encoding_methods.hpp" +#include "protocol_constants.hpp" + +namespace clp::ffi::ir_stream { +/** + * Serializes the given metadata into the IR stream. + * @param metadata + * @param output_buf + * @return Whether serialization succeeded. + */ +[[nodiscard]] auto +serialize_metadata(nlohmann::json& metadata, std::vector& output_buf) -> bool; + +/** + * Serializes the given integer into the IR stream. + * @tparam integer_t + * @param value + * @param output_buf + */ +template +auto serialize_int(integer_t value, std::vector& output_buf) -> void; + +/** + * Serializes a string using CLP's encoding for unstructured text. + * @tparam encoded_variable_t + * @param str + * @param logtype Returns the corresponding logtype. + * @param output_buf + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto serialize_clp_string( + std::string_view str, + std::string& logtype, + std::vector& output_buf +) -> bool; + +/** + * Serializes a string. + * @param str + * @param output_buf + * @return Whether serialization succeeded. + */ +[[nodiscard]] auto serialize_string(std::string_view str, std::vector& output_buf) -> bool; + +template +auto serialize_int(integer_t value, std::vector& output_buf) -> void { + integer_t value_big_endian{}; + static_assert(sizeof(integer_t) == 2 || sizeof(integer_t) == 4 || sizeof(integer_t) == 8); + if constexpr (sizeof(value) == 2) { + value_big_endian = bswap_16(value); + } else if constexpr (sizeof(value) == 4) { + value_big_endian = bswap_32(value); + } else if constexpr (sizeof(value) == 8) { + value_big_endian = bswap_64(value); + } + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + std::span const data_view{reinterpret_cast(&value_big_endian), sizeof(value)}; + output_buf.insert(output_buf.end(), data_view.begin(), data_view.end()); +} + +template +[[nodiscard]] auto serialize_clp_string( + std::string_view str, + std::string& logtype, + std::vector& output_buf +) -> bool { + static_assert( + (std::is_same_v + || std::is_same_v) + ); + bool succeeded{}; + if constexpr (std::is_same_v) { + output_buf.push_back(cProtocol::Payload::ValueFourByteEncodingClpStr); + succeeded = four_byte_encoding::serialize_message(str, logtype, output_buf); + } else { + output_buf.push_back(cProtocol::Payload::ValueEightByteEncodingClpStr); + succeeded = eight_byte_encoding::serialize_message(str, logtype, output_buf); + } + return succeeded; +} +} // namespace clp::ffi::ir_stream +#endif diff --git a/components/core/src/clp/ffi/utils.cpp b/components/core/src/clp/ffi/utils.cpp new file mode 100644 index 000000000..c85c47701 --- /dev/null +++ b/components/core/src/clp/ffi/utils.cpp @@ -0,0 +1,89 @@ +#include "utils.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../utf8_utils.hpp" + +using std::string; +using std::string_view; + +namespace clp::ffi { +auto validate_and_escape_utf8_string(string_view raw) -> std::optional { + std::optional ret_val; + auto& escaped{ret_val.emplace()}; + escaped.reserve(raw.size() + (raw.size() / 2)); + if (false == validate_and_append_escaped_utf8_string(raw, escaped)) { + return std::nullopt; + } + return ret_val; +} + +auto validate_and_append_escaped_utf8_string(std::string_view src, std::string& dst) -> bool { + string_view::const_iterator next_char_to_copy_it{src.cbegin()}; + + auto escape_handler = [&](string_view::const_iterator it) -> void { + // Allocate 6 + 1 size buffer to format control characters as "\u00bb", with the last byte + // used by `snprintf` to append '\0' + constexpr size_t cControlCharacterBufSize{7}; + std::array buf{}; + std::string_view escaped_char; + bool escape_required{true}; + switch (*it) { + case '\b': + escaped_char = "\\b"; + break; + case '\t': + escaped_char = "\\t"; + break; + case '\n': + escaped_char = "\\n"; + break; + case '\f': + escaped_char = "\\f"; + break; + case '\r': + escaped_char = "\\r"; + break; + case '\\': + escaped_char = "\\\\"; + break; + case '"': + escaped_char = "\\\""; + break; + default: { + constexpr uint8_t cLargestControlCharacter{0x1F}; + auto const byte{static_cast(*it)}; + if (cLargestControlCharacter >= byte) { + std::ignore = snprintf(buf.data(), buf.size(), "\\u00%02x", byte); + escaped_char = {buf.data(), buf.size() - 1}; + } else { + escape_required = false; + } + break; + } + } + if (escape_required) { + dst.append(next_char_to_copy_it, it); + dst += escaped_char; + next_char_to_copy_it = it + 1; + } + }; + + if (false == validate_utf8_string(src, escape_handler)) { + return false; + } + + if (src.cend() != next_char_to_copy_it) { + dst.append(next_char_to_copy_it, src.cend()); + } + + return true; +} +} // namespace clp::ffi diff --git a/components/core/src/clp/ffi/utils.hpp b/components/core/src/clp/ffi/utils.hpp new file mode 100644 index 000000000..26823da9c --- /dev/null +++ b/components/core/src/clp/ffi/utils.hpp @@ -0,0 +1,31 @@ +#ifndef CLP_FFI_UTILS_HPP +#define CLP_FFI_UTILS_HPP + +#include +#include +#include + +namespace clp::ffi { +/** + * Validates whether the given string is UTF-8 encoded, and escapes any characters to make the + * string compatible with the JSON specification. + * @param raw The raw string to escape. + * @return The escaped string on success. + * @return std::nullopt if the string contains any non-UTF-8-encoded byte sequences. + */ +[[nodiscard]] auto validate_and_escape_utf8_string(std::string_view raw +) -> std::optional; + +/** + * Validates whether `src` is UTF-8 encoded, and appends `src` to `dst` while escaping any + * characters to make the appended string compatible with the JSON specification. + * @param src The string to validate and escape. + * @param dst Returns `dst` with an escaped version of `src` appended. + * @return Whether `src` is a valid UTF-8-encoded string. NOTE: Even if `src` is not UTF-8 encoded, + * `dst` may be modified. + */ +[[nodiscard]] auto +validate_and_append_escaped_utf8_string(std::string_view src, std::string& dst) -> bool; +} // namespace clp::ffi + +#endif // CLP_FFI_UTILS_HPP diff --git a/components/core/src/clp/hash_utils.cpp b/components/core/src/clp/hash_utils.cpp new file mode 100644 index 000000000..a70cd8ff6 --- /dev/null +++ b/components/core/src/clp/hash_utils.cpp @@ -0,0 +1,223 @@ +#include "hash_utils.hpp" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +using std::make_unique; +using std::span; +using std::string; +using std::unique_ptr; +using std::vector; + +namespace clp { +namespace { +/** + * Pops the first OpenSSL error from its error queue and gets its string representation. + * @return The string representing the first OpenSSL error from its error queue. + */ +auto get_openssl_error_string() -> string { + auto const openssl_err = ERR_get_error(); + if (0 == openssl_err) { + return {}; + } + auto* openssl_err_str = ERR_error_string(openssl_err, nullptr); + if (nullptr == openssl_err_str) { + return {"Error has no string representation, error_code: " + std::to_string(openssl_err)}; + } + return {openssl_err_str}; +} + +/** + * A C++ wrapper for OpenSSL's EVP message digest context (EVP_MD_CTX). + */ +class EvpDigestContext { +public: + // Types + class OperationFailed : public clp::TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : OperationFailed( + error_code, + filename, + line_number, + "EvpDigestContext operation failed" + ) {} + + OperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + string message + ) + : TraceableException(error_code, filename, line_number), + m_message(std::move(message)) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { + return m_message.c_str(); + } + + private: + string m_message; + }; + + // Constructors + /** + * @param type The type of digest (hash algorithm). + * @throw EvpDigestContext::OperationFailed with ErrorCode_NoMem if `EVP_MD_CTX_create` fails. + * @throw EvpDigestContext::OperationFailed with ErrorCode_Failure if `EVP_DigestInit_ex` fails. + */ + explicit EvpDigestContext(EVP_MD const* type) + : m_md_ctx{EVP_MD_CTX_create()}, + m_digest_nid{EVP_MD_type(type)} { + if (nullptr == m_md_ctx) { + throw OperationFailed(ErrorCode_NoMem, __FILENAME__, __LINE__); + } + // Set impl to nullptr to use the default implementation of digest type + if (1 != EVP_DigestInit_ex(m_md_ctx, type, nullptr)) { + throw OperationFailed( + ErrorCode_Failure, + __FILENAME__, + __LINE__, + get_openssl_error_string() + ); + } + } + + // Disable copy constructor and assignment operator + EvpDigestContext(EvpDigestContext const&) = delete; + auto operator=(EvpDigestContext const&) -> EvpDigestContext& = delete; + + // Disable move constructor and assignment operator + EvpDigestContext(EvpDigestContext&&) = delete; + auto operator=(EvpDigestContext&&) -> EvpDigestContext& = delete; + + // Destructor + ~EvpDigestContext() { EVP_MD_CTX_destroy(m_md_ctx); } + + // Methods + /** + * Hashes `input` into the digest. + * @param input + * @return Whether `EVP_DigestUpdate` succeeded. + */ + [[nodiscard]] auto digest_update(span input) -> bool; + + /** + * Writes the digest into `hash` and clears the digest. + * @param hash Returns the hashing result. + * @return ErrorCode_Success on success. + * @return ErrorCode_Corrupt if `hash` has an unexpected length. + * @return ErrorCode_Failure if `EVP_DigestFinal_ex` fails. + * @throw EvpDigestContext::OperationFailed with ErrorCode_Failure if `EVP_DigestInit_ex` fails. + */ + [[nodiscard]] auto digest_final(vector& hash) -> ErrorCode; + +private: + EVP_MD_CTX* m_md_ctx{nullptr}; + int m_digest_nid{}; +}; + +auto EvpDigestContext::digest_update(span input) -> bool { + if (1 != EVP_DigestUpdate(m_md_ctx, input.data(), input.size())) { + return false; + } + return true; +} + +auto EvpDigestContext::digest_final(vector& hash) -> ErrorCode { + hash.resize(EVP_MD_CTX_size(m_md_ctx)); + unsigned int length{}; + if (1 != EVP_DigestFinal_ex(m_md_ctx, hash.data(), &length)) { + return ErrorCode_Failure; + } + if (hash.size() != length) { + return ErrorCode_Corrupt; + } + + if (1 != EVP_DigestInit_ex(m_md_ctx, EVP_get_digestbynid(m_digest_nid), nullptr)) { + throw OperationFailed( + ErrorCode_Failure, + __FILENAME__, + __LINE__, + get_openssl_error_string() + ); + } + return ErrorCode_Success; +} +} // namespace + +auto convert_to_hex_string(span input) -> string { + string hex_string; + for (auto const c : input) { + hex_string += fmt::format("{:02x}", c); + } + return hex_string; +} + +auto get_hmac_sha256_hash( + span input, + span key, + vector& hash +) -> ErrorCode { + if (key.size() > INT32_MAX) { + return ErrorCode_BadParam; + } + + hash.resize(SHA256_DIGEST_LENGTH); + unsigned int hash_length{0}; + auto const key_length{static_cast(key.size())}; + if (nullptr + == HMAC(EVP_sha256(), + key.data(), + key_length, + input.data(), + input.size(), + hash.data(), + &hash_length)) + { + return ErrorCode_Failure; + } + + if (hash.size() != hash_length) { + return ErrorCode_Corrupt; + } + + return ErrorCode_Success; +} + +auto get_sha256_hash(span input, vector& hash) -> ErrorCode { + unique_ptr evp_ctx_manager; + try { + evp_ctx_manager = make_unique(EVP_sha256()); + } catch (EvpDigestContext::OperationFailed const& err) { + throw HashUtilsOperationFailed(err.get_error_code(), __FILENAME__, __LINE__, err.what()); + } + + if (false == evp_ctx_manager->digest_update(input)) { + return ErrorCode_Failure; + } + + if (auto const error_code = evp_ctx_manager->digest_final(hash); + ErrorCode_Success != error_code) + { + return error_code; + } + + return ErrorCode_Success; +} +} // namespace clp diff --git a/components/core/src/clp/hash_utils.hpp b/components/core/src/clp/hash_utils.hpp new file mode 100644 index 000000000..4606aed23 --- /dev/null +++ b/components/core/src/clp/hash_utils.hpp @@ -0,0 +1,77 @@ +#ifndef CLP_HASH_UTILS_HPP +#define CLP_HASH_UTILS_HPP + +#include +#include +#include +#include + +#include "ErrorCode.hpp" +#include "TraceableException.hpp" + +namespace clp { +// Types +class HashUtilsOperationFailed : public TraceableException { +public: + // Constructors + HashUtilsOperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : HashUtilsOperationFailed( + error_code, + filename, + line_number, + "clp::hash_utils operation failed" + ) {} + + HashUtilsOperationFailed( + ErrorCode error_code, + char const* const filename, + int line_number, + std::string message + ) + : TraceableException(error_code, filename, line_number), + m_message(std::move(message)) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { return m_message.c_str(); } + +private: + std::string m_message; +}; + +/** + * @param input + * @return `input` as a hex string (without the "0x" prefix). + */ +[[nodiscard]] auto convert_to_hex_string(std::span input) -> std::string; + +/** + * Gets the HMAC-SHA256 hash of `input` with `key`. + * @param input + * @param key + * @param hash Returns the HMAC. + * @return ErrorCode_Success on success. + * @return ErrorCode_BadParam if `key` is longer than `INT32_MAX`. + * @return ErrorCode_Failure if hash generation fails. + * @return ErrorCode_Corrupt if `hash` has an unexpected length. + */ +[[nodiscard]] auto get_hmac_sha256_hash( + std::span input, + std::span key, + std::vector& hash +) -> ErrorCode; + +/** + * Gets the SHA256 hash of `input`. + * @param input + * @param hash Returns the hash. + * @return ErrorCode_Success on success. + * @return ErrorCode_Failure if `EvpDigestContext::digest_update` fails. + * @return Same as `EvpDigestContext::digest_final` if `EvpDigestContext::digest_final` fails. + * @throw HashUtilsOperationFailed if an OpenSSL EVP digest couldn't be created. + */ +[[nodiscard]] auto get_sha256_hash( + std::span input, + std::vector& hash +) -> ErrorCode; +} // namespace clp +#endif // CLP_HASH_UTILS_HPP diff --git a/components/core/src/clp/ir/EncodedTextAst.cpp b/components/core/src/clp/ir/EncodedTextAst.cpp new file mode 100644 index 000000000..f0ee4d493 --- /dev/null +++ b/components/core/src/clp/ir/EncodedTextAst.cpp @@ -0,0 +1,57 @@ +#include "EncodedTextAst.hpp" + +#include +#include +#include + +#include "../ffi/encoding_methods.hpp" +#include "ffi/ir_stream/decoding_methods.hpp" + +using clp::ffi::decode_float_var; +using clp::ffi::decode_integer_var; +using clp::ffi::ir_stream::DecodingException; +using clp::ffi::ir_stream::generic_decode_message; +using std::optional; +using std::string; + +namespace clp::ir { +template +auto EncodedTextAst::decode_and_unparse() const -> optional { + string decoded_string; + + auto constant_handler = [&](string const& value, size_t begin_pos, size_t length) { + decoded_string.append(value, begin_pos, length); + }; + + auto encoded_int_handler + = [&](encoded_variable_t value) { decoded_string.append(decode_integer_var(value)); }; + + auto encoded_float_handler = [&](encoded_variable_t encoded_float) { + decoded_string.append(decode_float_var(encoded_float)); + }; + + auto dict_var_handler = [&](string const& dict_var) { decoded_string.append(dict_var); }; + + try { + generic_decode_message( + m_logtype, + m_encoded_vars, + m_dict_vars, + constant_handler, + encoded_int_handler, + encoded_float_handler, + dict_var_handler + ); + } catch (DecodingException const& e) { + return std::nullopt; + } + return std::make_optional(decoded_string); +} + +// Explicitly declare template specializations so that we can define the template methods in this +// file +template auto EncodedTextAst::decode_and_unparse( +) const -> optional; +template auto EncodedTextAst::decode_and_unparse( +) const -> optional; +} // namespace clp::ir diff --git a/components/core/src/clp/ir/EncodedTextAst.hpp b/components/core/src/clp/ir/EncodedTextAst.hpp new file mode 100644 index 000000000..a3795ceea --- /dev/null +++ b/components/core/src/clp/ir/EncodedTextAst.hpp @@ -0,0 +1,69 @@ +#ifndef CLP_IR_ENCODEDTEXTAST_HPP +#define CLP_IR_ENCODEDTEXTAST_HPP + +#include +#include +#include +#include + +#include "types.hpp" + +namespace clp::ir { +/** + * A parsed and encoded unstructured text string. + * @tparam encoded_variable_t The type of encoded variables in the string. + */ +template +class EncodedTextAst { +public: + // Constructor + explicit EncodedTextAst( + std::string logtype, + std::vector dict_vars, + std::vector encoded_vars + ) + : m_logtype{std::move(logtype)}, + m_dict_vars{std::move(dict_vars)}, + m_encoded_vars{std::move(encoded_vars)} {} + + // Disable copy constructor and assignment operator + EncodedTextAst(EncodedTextAst const&) = delete; + auto operator=(EncodedTextAst const&) -> EncodedTextAst& = delete; + + // Default move constructor and assignment operator + EncodedTextAst(EncodedTextAst&&) = default; + auto operator=(EncodedTextAst&&) -> EncodedTextAst& = default; + + // Destructor + ~EncodedTextAst() = default; + + // Methods + [[nodiscard]] auto get_logtype() const -> std::string const& { return m_logtype; } + + [[nodiscard]] auto get_dict_vars() const -> std::vector const& { + return m_dict_vars; + } + + [[nodiscard]] auto get_encoded_vars() const -> std::vector const& { + return m_encoded_vars; + } + + /** + * Decodes and un-parses the EncodedTextAst into its string form. + * @return The string corresponding to the EncodedTextAst on success. + * @return std::nullopt if decoding fails. + */ + [[nodiscard]] auto decode_and_unparse() const -> std::optional; + +private: + // Variables + std::string m_logtype; + std::vector m_dict_vars; + std::vector m_encoded_vars; +}; + +using EightByteEncodedTextAst = EncodedTextAst; +using FourByteEncodedTextAst = EncodedTextAst; +} // namespace clp::ir + +#endif // CLP_IR_ENCODEDTEXTAST_HPP diff --git a/components/core/src/clp/ir/LogEvent.hpp b/components/core/src/clp/ir/LogEvent.hpp index d32aabb41..4a3ef7567 100644 --- a/components/core/src/clp/ir/LogEvent.hpp +++ b/components/core/src/clp/ir/LogEvent.hpp @@ -2,9 +2,10 @@ #define CLP_IR_LOGEVENT_HPP #include +#include #include -#include "../Defs.h" +#include "EncodedTextAst.hpp" #include "time_types.hpp" #include "types.hpp" @@ -20,38 +21,26 @@ class LogEvent { LogEvent( epoch_time_ms_t timestamp, UtcOffset utc_offset, - std::string logtype, - std::vector dict_vars, - std::vector encoded_vars + EncodedTextAst message ) : m_timestamp{timestamp}, m_utc_offset{utc_offset}, - m_logtype{std::move(logtype)}, - m_dict_vars{std::move(dict_vars)}, - m_encoded_vars{std::move(encoded_vars)} {} + m_message{std::move(message)} {} // Methods [[nodiscard]] auto get_timestamp() const -> epoch_time_ms_t { return m_timestamp; } [[nodiscard]] auto get_utc_offset() const -> UtcOffset { return m_utc_offset; } - [[nodiscard]] auto get_logtype() const -> std::string const& { return m_logtype; } - - [[nodiscard]] auto get_dict_vars() const -> std::vector const& { - return m_dict_vars; - } - - [[nodiscard]] auto get_encoded_vars() const -> std::vector const& { - return m_encoded_vars; + [[nodiscard]] auto get_message() const -> EncodedTextAst const& { + return m_message; } private: // Variables epoch_time_ms_t m_timestamp{0}; UtcOffset m_utc_offset{0}; - std::string m_logtype; - std::vector m_dict_vars; - std::vector m_encoded_vars; + EncodedTextAst m_message; }; } // namespace clp::ir diff --git a/components/core/src/clp/ir/LogEventDeserializer.cpp b/components/core/src/clp/ir/LogEventDeserializer.cpp index b158a2712..6106568dd 100644 --- a/components/core/src/clp/ir/LogEventDeserializer.cpp +++ b/components/core/src/clp/ir/LogEventDeserializer.cpp @@ -3,16 +3,18 @@ #include #include +#include #include #include "../ffi/ir_stream/decoding_methods.hpp" #include "../ffi/ir_stream/protocol_constants.hpp" +#include "EncodedTextAst.hpp" #include "types.hpp" namespace clp::ir { template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { ffi::ir_stream::encoded_tag_t metadata_type{0}; std::vector metadata; auto ir_error_code = ffi::ir_stream::deserialize_preamble(reader, metadata_type, metadata); @@ -68,7 +70,7 @@ auto LogEventDeserializer::create(ReaderInterface& reader template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { // Process any packets before the log event ffi::ir_stream::encoded_tag_t tag{}; while (true) { @@ -123,17 +125,21 @@ auto LogEventDeserializer::deserialize_log_event( timestamp = m_prev_msg_timestamp; } - return LogEvent{timestamp, m_utc_offset, logtype, dict_vars, encoded_vars}; + return LogEvent{ + timestamp, + m_utc_offset, + EncodedTextAst{logtype, dict_vars, encoded_vars} + }; } // Explicitly declare template specializations so that we can define the template methods in this // file template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; } // namespace clp::ir diff --git a/components/core/src/clp/ir/LogEventDeserializer.hpp b/components/core/src/clp/ir/LogEventDeserializer.hpp index 5fb72579d..a8bb4dc20 100644 --- a/components/core/src/clp/ir/LogEventDeserializer.hpp +++ b/components/core/src/clp/ir/LogEventDeserializer.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "../ReaderInterface.hpp" #include "../time_types.hpp" @@ -35,7 +35,7 @@ class LogEventDeserializer { * or uses an unsupported version */ static auto create(ReaderInterface& reader - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; // Delete copy constructor and assignment LogEventDeserializer(LogEventDeserializer const&) = delete; @@ -62,7 +62,7 @@ class LogEventDeserializer { * - std::errc::protocol_error if the IR stream is corrupted */ [[nodiscard]] auto deserialize_log_event( - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; private: // Constructors diff --git a/components/core/src/clp/ir/LogEventSerializer.cpp b/components/core/src/clp/ir/LogEventSerializer.cpp new file mode 100644 index 000000000..13c94714c --- /dev/null +++ b/components/core/src/clp/ir/LogEventSerializer.cpp @@ -0,0 +1,160 @@ +#include "LogEventSerializer.hpp" + +#include +#include + +#include + +#include "../Defs.h" +#include "../ErrorCode.hpp" +#include "../ffi/ir_stream/encoding_methods.hpp" +#include "../ffi/ir_stream/protocol_constants.hpp" +#include "../ir/types.hpp" +#include "../type_utils.hpp" + +using std::string; +using std::string_view; + +namespace clp::ir { +template +LogEventSerializer::~LogEventSerializer() { + if (m_is_open) { + SPDLOG_ERROR("clp::ir::LogEventSerializer not closed before being destroyed - output maybe " + "corrupted."); + } +} + +template +auto LogEventSerializer::open(string const& file_path) -> bool { + if (m_is_open) { + throw OperationFailed(ErrorCode_NotReady, __FILENAME__, __LINE__); + } + + m_serialized_size = 0; + m_num_log_events = 0; + m_ir_buf.clear(); + + m_writer.open(file_path, FileWriter::OpenMode::CREATE_FOR_WRITING); + m_zstd_compressor.open(m_writer); + + bool res{}; + if constexpr (std::is_same_v) { + m_prev_event_timestamp = 0; + res = ffi::ir_stream::four_byte_encoding::serialize_preamble( + cTimestampPattern, + cTimestampPatternSyntax, + cTimezoneID, + m_prev_event_timestamp, + m_ir_buf + ); + } else { + res = clp::ffi::ir_stream::eight_byte_encoding::serialize_preamble( + cTimestampPattern, + cTimestampPatternSyntax, + cTimezoneID, + m_ir_buf + ); + } + + if (false == res) { + close_writer(); + return false; + } + + m_is_open = true; + + // Flush the preamble + flush(); + + return true; +} + +template +auto LogEventSerializer::flush() -> void { + if (false == m_is_open) { + throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); + } + m_zstd_compressor.write( + size_checked_pointer_cast(m_ir_buf.data()), + m_ir_buf.size() + ); + m_ir_buf.clear(); +} + +template +auto LogEventSerializer::close() -> void { + if (false == m_is_open) { + throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); + } + m_ir_buf.push_back(clp::ffi::ir_stream::cProtocol::Eof); + flush(); + close_writer(); + m_is_open = false; +} + +template +auto LogEventSerializer::serialize_log_event( + epoch_time_ms_t timestamp, + string_view message +) -> bool { + if (false == m_is_open) { + throw OperationFailed(ErrorCode_NotInit, __FILENAME__, __LINE__); + } + + string logtype; + bool res{}; + auto const buf_size_before_serialization = m_ir_buf.size(); + if constexpr (std::is_same_v) { + res = clp::ffi::ir_stream::eight_byte_encoding::serialize_log_event( + timestamp, + message, + logtype, + m_ir_buf + ); + } else { + auto const timestamp_delta = timestamp - m_prev_event_timestamp; + m_prev_event_timestamp = timestamp; + res = clp::ffi::ir_stream::four_byte_encoding::serialize_log_event( + timestamp_delta, + message, + logtype, + m_ir_buf + ); + } + if (false == res) { + return false; + } + m_serialized_size += m_ir_buf.size() - buf_size_before_serialization; + ++m_num_log_events; + return true; +} + +template +auto LogEventSerializer::close_writer() -> void { + m_zstd_compressor.close(); + m_writer.close(); +} + +// Explicitly declare template specializations so that we can define the template methods in this +// file +template LogEventSerializer::~LogEventSerializer(); +template LogEventSerializer::~LogEventSerializer(); +template auto LogEventSerializer::open(string const& file_path +) -> bool; +template auto LogEventSerializer::open(string const& file_path +) -> bool; +template auto LogEventSerializer::flush() -> void; +template auto LogEventSerializer::flush() -> void; +template auto LogEventSerializer::close() -> void; +template auto LogEventSerializer::close() -> void; +template auto LogEventSerializer::serialize_log_event( + epoch_time_ms_t timestamp, + string_view message +) -> bool; +template auto LogEventSerializer::serialize_log_event( + epoch_time_ms_t timestamp, + string_view message +) -> bool; +template auto LogEventSerializer::close_writer() -> void; +template auto LogEventSerializer::close_writer() -> void; +} // namespace clp::ir diff --git a/components/core/src/clp/ir/LogEventSerializer.hpp b/components/core/src/clp/ir/LogEventSerializer.hpp new file mode 100644 index 000000000..635ecca9f --- /dev/null +++ b/components/core/src/clp/ir/LogEventSerializer.hpp @@ -0,0 +1,129 @@ +#ifndef CLP_IR_LOGEVENTSERIALIZER_HPP +#define CLP_IR_LOGEVENTSERIALIZER_HPP + +#include +#include +#include +#include +#include +#include + +#include "../ErrorCode.hpp" +#include "../FileWriter.hpp" +#include "../streaming_compression/zstd/Compressor.hpp" +#include "../TraceableException.hpp" +#include "../type_utils.hpp" +#include "types.hpp" + +namespace clp::ir { +/** + * Class for serializing log events into a Zstandard-compressed IR stream. The serializer first + * buffers the serialized data into an internal buffer, and only flushes the buffered IR to disk + * when `flush` or `close` is called. + */ +template +class LogEventSerializer { +public: + // Types + class OperationFailed : public TraceableException { + public: + // Constructors + OperationFailed(ErrorCode error_code, char const* const filename, int line_number) + : TraceableException(error_code, filename, line_number) {} + + // Methods + [[nodiscard]] auto what() const noexcept -> char const* override { + return "clp::ir::LogEventSerializer operation failed"; + } + }; + + // Constructors + LogEventSerializer() = default; + + // Delete copy constructor and assignment + LogEventSerializer(LogEventSerializer const&) = delete; + auto operator=(LogEventSerializer const&) -> LogEventSerializer& = delete; + + // Define default move constructor and assignment + LogEventSerializer(LogEventSerializer&&) = default; + auto operator=(LogEventSerializer&&) -> LogEventSerializer& = default; + + ~LogEventSerializer(); + + /** + * Creates a Zstandard-compressed IR file on disk, and writes the IR file's preamble. + * @param file_path + * @return true on success, false if serializing the preamble fails + * @throw FileWriter::OperationFailed if the FileWriter fails to open the file specified by + * file_path + * @throw streaming_compression::zstd::Compressor if the Zstandard compressor couldn't be opened + * @throw ir::LogEventSerializer::OperationFailed if an IR file is already open + */ + [[nodiscard]] auto open(std::string const& file_path) -> bool; + + /** + * Flushes any buffered data. + * @throw ir::LogEventSerializer::OperationFailed if no IR file is open + */ + auto flush() -> void; + + /** + * Serializes the EoF tag, flushes the buffer, and closes the current IR stream. + * @throw ir::LogEventSerializer::OperationFailed if no IR file is open + */ + auto close() -> void; + + /** + * @return Size of serialized data in bytes + */ + [[nodiscard]] auto get_serialized_size() const -> size_t { return m_serialized_size; } + + /** + * @return Number of serialized log events. + */ + [[nodiscard]] auto get_num_log_events() const -> size_t { return m_num_log_events; } + + /** + * Serializes the given log event. + * @return Whether the log event was successfully serialized. + */ + [[nodiscard]] auto + serialize_log_event(epoch_time_ms_t timestamp, std::string_view message) -> bool; + +private: + // Constants + // NOTE: IR files currently store the log's timestamp pattern and timezone ID. However: + // - files in CLP archives don't currently support encoding time zones; + // - IR files don't support multiple timestamp patterns whereas files in CLP archives do; + // - No consumers of IR files currently use these fields. + // Accordingly, we store a default timestamp pattern and timezone ID in the IR file's metadata, + // but it is really up to consumers of the IR file to use an appropriate timestamp pattern and + // timezone when rendering log events. + static constexpr std::string_view cTimestampPattern{"%Y-%m-%d %H:%M:%S,%3"}; + static constexpr std::string_view cTimestampPatternSyntax{}; + static constexpr std::string_view cTimezoneID{"UTC"}; + + // Methods + /** + * Closes the member compressor and file writer in the proper order. + */ + auto close_writer() -> void; + + // Variables + size_t m_num_log_events{0}; + size_t m_serialized_size{0}; // Bytes + + [[no_unique_address]] std::conditional_t< + std::is_same_v, + epoch_time_ms_t, + EmptyType> m_prev_event_timestamp{}; + + std::vector m_ir_buf; + FileWriter m_writer; + streaming_compression::zstd::Compressor m_zstd_compressor; + + bool m_is_open{false}; +}; +} // namespace clp::ir + +#endif // CLP_IR_LOGEVENTSERIALIZER_HPP diff --git a/components/core/src/clp/ir/constants.hpp b/components/core/src/clp/ir/constants.hpp new file mode 100644 index 000000000..2c287c2e0 --- /dev/null +++ b/components/core/src/clp/ir/constants.hpp @@ -0,0 +1,10 @@ +#ifndef CLP_IR_CONSTANTS_HPP +#define CLP_IR_CONSTANTS_HPP + +#include + +namespace clp::ir { +constexpr std::string_view cIrFileExtension{".clp.zst"}; +} // namespace clp::ir + +#endif // CLP_IR_CONSTANTS_HPP diff --git a/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt b/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt index 6dc5334bf..fd62a39fb 100644 --- a/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt +++ b/components/core/src/clp/make_dictionaries_readable/CMakeLists.txt @@ -4,6 +4,8 @@ set( ../dictionary_utils.hpp ../DictionaryEntry.hpp ../DictionaryReader.hpp + ../FileDescriptor.cpp + ../FileDescriptor.hpp ../FileReader.cpp ../FileReader.hpp ../FileWriter.cpp @@ -17,6 +19,8 @@ set( ../ParsedMessage.hpp ../ReaderInterface.cpp ../ReaderInterface.hpp + ../ReadOnlyMemoryMappedFile.cpp + ../ReadOnlyMemoryMappedFile.hpp ../spdlog_with_specializations.hpp ../streaming_compression/Decompressor.hpp ../streaming_compression/passthrough/Decompressor.cpp diff --git a/components/core/src/clp/regex_utils/CMakeLists.txt b/components/core/src/clp/regex_utils/CMakeLists.txt new file mode 100644 index 000000000..c5d54dde0 --- /dev/null +++ b/components/core/src/clp/regex_utils/CMakeLists.txt @@ -0,0 +1,20 @@ +set( + REGEX_UTILS_HEADER_LIST + "constants.hpp" + "ErrorCode.hpp" + "regex_translation_utils.hpp" + "RegexToWildcardTranslatorConfig.hpp" +) +add_library( + regex_utils + ErrorCode.cpp + regex_translation_utils.cpp + ${REGEX_UTILS_HEADER_LIST} +) +add_library(clp::regex_utils ALIAS regex_utils) +target_include_directories(regex_utils + PRIVATE + ../ + "${PROJECT_SOURCE_DIR}/submodules" +) +target_compile_features(regex_utils PRIVATE cxx_std_20) diff --git a/components/core/src/clp/regex_utils/ErrorCode.cpp b/components/core/src/clp/regex_utils/ErrorCode.cpp new file mode 100644 index 000000000..4e24e9a8b --- /dev/null +++ b/components/core/src/clp/regex_utils/ErrorCode.cpp @@ -0,0 +1,93 @@ +#include "regex_utils/ErrorCode.hpp" + +#include +#include +#include + +namespace clp::regex_utils { +using std::error_code; + +namespace { +using std::error_category; +using std::string; +using std::string_view; + +/** + * Class for giving the error codes more detailed string descriptions. + */ +class ErrorCodeCategory : public error_category { +public: + /** + * @return The class of errors. + */ + [[nodiscard]] auto name() const noexcept -> char const* override; + + /** + * @param The error code encoded in int. + * @return The descriptive message for the error. + */ + [[nodiscard]] auto message(int ev) const -> string override; +}; + +auto ErrorCodeCategory::name() const noexcept -> char const* { + return "regex utility"; +} + +auto ErrorCodeCategory::message(int ev) const -> string { + switch (static_cast(ev)) { + case ErrorCode::Success: + return "Success."; + + case ErrorCode::IllegalState: + return "Unrecognized state."; + + case ErrorCode::UntranslatableStar: + return "Unable to express regex quantifier `*` in wildcard, which repeats a token for " + "zero or more occurences, unless it is combined with a wildcard `.`"; + + case ErrorCode::UntranslatablePlus: + return "Unable to express regex quantifier `+` in wildcard, which repeats a token for " + "one or more occurences, unless it is combined with a wildcard `.`"; + + case ErrorCode::UnsupportedQuestionMark: + return "Unable to express regex quantifier `?` in wildcard, which makes the preceding " + "token optional, unless the translator supports returning a list of possible " + "wildcard translations."; + + case ErrorCode::UnsupportedPipe: + return "Unable to express regex OR `|` in wildcard, which allows the query string to " + "match a single token out of a series of options, unless the translator " + "supports returning a list of possible wildcard translations."; + + case ErrorCode::IllegalCaret: + return "Failed to translate due to start anchor `^` in the middle of the string."; + + case ErrorCode::IllegalDollarSign: + return "Failed to translate due to end anchor `$` in the middle of the string."; + + case ErrorCode::IllegalEscapeSequence: + return "Currently only supports escape sequences that are used to suppress special " + "meanings of regex metacharacters. Alphanumeric characters are disallowed."; + + case ErrorCode::UnmatchedParenthesis: + return "Unmatched opening `(` or closing `)`."; + + case ErrorCode::IncompleteCharsetStructure: + return "Unmatched closing `]` at the end of the string."; + + case ErrorCode::UnsupportedCharsetPattern: + return "Currently only supports character set that can be reduced to a single " + "character."; + + default: + return "(unrecognized error)"; + } +} + +ErrorCodeCategory const cErrorCodeCategoryInstance; +} // namespace + +auto make_error_code(ErrorCode e) -> error_code { + return {static_cast(e), cErrorCodeCategoryInstance}; +} +} // namespace clp::regex_utils diff --git a/components/core/src/clp/regex_utils/ErrorCode.hpp b/components/core/src/clp/regex_utils/ErrorCode.hpp new file mode 100644 index 000000000..9b4fbf8f2 --- /dev/null +++ b/components/core/src/clp/regex_utils/ErrorCode.hpp @@ -0,0 +1,42 @@ +#ifndef CLP_REGEX_UTILS_ERRORCODE_HPP +#define CLP_REGEX_UTILS_ERRORCODE_HPP + +#include +#include +#include + +namespace clp::regex_utils { +/** + * Enum class for propagating and handling various regex utility errors. + * More detailed descriptions can be found in ErrorCode.cpp. + */ +enum class ErrorCode : uint8_t { + Success = 0, + IllegalState, + UntranslatableStar, + UntranslatablePlus, + UnsupportedQuestionMark, + UnsupportedPipe, + IllegalCaret, + IllegalDollarSign, + IllegalEscapeSequence, + UnmatchedParenthesis, + IncompleteCharsetStructure, + UnsupportedCharsetPattern, +}; + +/** + * Wrapper function to turn a regular enum class into an std::error_code. + * + * @param An error code enum. + * @return The corresponding std::error_code type variable. + */ +[[nodiscard]] auto make_error_code(ErrorCode ec) -> std::error_code; +} // namespace clp::regex_utils + +namespace std { +template <> +struct is_error_code_enum : true_type {}; +} // namespace std + +#endif // CLP_REGEX_UTILS_ERRORCODE_HPP diff --git a/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp b/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp new file mode 100644 index 000000000..e53963c2e --- /dev/null +++ b/components/core/src/clp/regex_utils/RegexToWildcardTranslatorConfig.hpp @@ -0,0 +1,46 @@ +#ifndef CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP +#define CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP + +namespace clp::regex_utils { +/** + * Allows users to customize and fine tune how to translate a regex string to wildcard. + * + * This class won't affect the core logic and state trasition mechanics of the regex to wildcard + * translator, but it can make the translator more versatile. For detailed descriptions of how each + * option should be used, see the getter function docstrings. + */ +class RegexToWildcardTranslatorConfig { +public: + RegexToWildcardTranslatorConfig( + bool case_insensitive_wildcard, + bool add_prefix_suffix_wildcards + ) + : m_case_insensitive_wildcard{case_insensitive_wildcard}, + m_add_prefix_suffix_wildcards{add_prefix_suffix_wildcards} {}; + + /** + * @return True if the final translated wildcard string will be fed into a case-insensitive + * wildcard analyzer. In such cases, we can safely translate charset patterns such as [aA] [Bb] + * into singular lowercase characters a, b. + */ + [[nodiscard]] auto case_insensitive_wildcard() const -> bool { + return m_case_insensitive_wildcard; + } + + /** + * @return True if in the absense of starting or ending anchors in the regex string, we append + * prefix or suffix zero or more characters wildcards. In other words, this config is true if + * the search is a substring search, and false if the search is an exact search. + */ + [[nodiscard]] auto add_prefix_suffix_wildcards() const -> bool { + return m_add_prefix_suffix_wildcards; + } + +private: + // Variables + bool m_case_insensitive_wildcard; + bool m_add_prefix_suffix_wildcards; +}; +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_REGEXTOWILDCARDTRANSLATORCONFIG_HPP diff --git a/components/core/src/clp/regex_utils/constants.hpp b/components/core/src/clp/regex_utils/constants.hpp new file mode 100644 index 000000000..ff2eb5b10 --- /dev/null +++ b/components/core/src/clp/regex_utils/constants.hpp @@ -0,0 +1,52 @@ +#ifndef CLP_REGEX_UTILS_CONSTANTS_HPP +#define CLP_REGEX_UTILS_CONSTANTS_HPP + +#include +#include +#include + +namespace clp::regex_utils { +constexpr size_t cCharBitarraySize = 128; + +/** + * Creates an ASCII character lookup table at compile time. + * + * @param char_str A string that contains the characters to look up. + * @return The lookup table as bit array. + */ +[[nodiscard]] constexpr auto create_char_bit_array(std::string_view char_str +) -> std::array { + std::array bit_array{}; + bit_array.fill(false); + for (auto const ch : char_str) { + bit_array.at(ch) = true; + } + return bit_array; +} + +// Wildcard meta characters +constexpr char cZeroOrMoreCharsWildcard{'*'}; +constexpr char cSingleCharWildcard{'?'}; + +// Regex meta characters +constexpr char cRegexZeroOrMore{'*'}; +constexpr char cRegexOneOrMore{'+'}; +constexpr char cRegexZeroOrOne{'?'}; +constexpr char cRegexStartAnchor{'^'}; +constexpr char cRegexEndAnchor{'$'}; +constexpr char cEscapeChar{'\\'}; +constexpr char cCharsetNegate{'^'}; + +// Character bitmaps +// The set of regex metacharacters that can be preceded with an escape backslash to be treated as a +// literal. +constexpr auto cRegexEscapeSeqMetaCharsLut = create_char_bit_array("*+?|^$.{}[]()<>-_/=!\\"); +// The set of wildcard metacharacters that must remain escaped in the translated string to be +// treated as a literal. +constexpr auto cWildcardMetaCharsLut = create_char_bit_array("?*\\"); +// The set of metacharacters that can be preceded with an escape backslash in the regex character +// set to be treated as a literal. +constexpr auto cRegexCharsetEscapeSeqMetaCharsLut = create_char_bit_array("^-]\\"); +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_CONSTANTS_HPP diff --git a/components/core/src/clp/regex_utils/regex_translation_utils.cpp b/components/core/src/clp/regex_utils/regex_translation_utils.cpp new file mode 100644 index 000000000..f1a987006 --- /dev/null +++ b/components/core/src/clp/regex_utils/regex_translation_utils.cpp @@ -0,0 +1,425 @@ +#include "regex_utils/regex_translation_utils.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include "regex_utils/constants.hpp" +#include "regex_utils/ErrorCode.hpp" +#include "regex_utils/RegexToWildcardTranslatorConfig.hpp" + +namespace clp::regex_utils { +using clp::string_utils::is_alphabet; +using std::error_code; +using std::optional; +using std::string; +using std::string_view; + +namespace { +/** + * Class for storing regex translation analysis states, capture group, quantifier information, etc. + */ +class TranslatorState { +public: + /** + * States for which we apply specific rules to translate encountered regex patterns. + * + * This list may be expanded as the translator supports translating more regex patterns. + *
    + *
  • Normal: The initial state, where characters have no special meanings and are treated + * literally.
  • + *
  • Dot: Encountered a period `.`. Expecting wildcard expression.
  • + *
  • Escaped: Encountered a backslash `\`. Expecting an escape sequence.
  • + *
  • Charset: Encountered an opening square bracket `[`. Expecting a character set.
  • + *
  • CharsetEscaped: Encountered an escape backslash in the character set.
  • + *
  • End: Encountered a dollar sign `$`, meaning the regex string has reached the end + * anchor.
  • + *
+ */ + enum class RegexPatternState : uint8_t { + Normal = 0, + Dot, + Escaped, + Charset, + CharsetEscaped, + End, + }; + + // Constructor + TranslatorState() = default; + + // Getters + [[nodiscard]] auto get_state() const -> RegexPatternState { return m_state; } + + [[nodiscard]] auto get_charset_begin_it() const -> optional { + return m_charset_begin_it; + } + + // Setters + auto set_next_state(RegexPatternState const& state) -> void { m_state = state; } + + auto set_charset_begin_it(string_view::const_iterator charset_begin_it) -> void { + m_charset_begin_it = charset_begin_it; + } + + auto invalidate_charset_begin_it() -> void { m_charset_begin_it.reset(); } + +private: + // Members + RegexPatternState m_state{RegexPatternState::Normal}; + optional m_charset_begin_it; +}; + +/** + * Functions that handle current-state-specific tasks before transitioning to the next state. + * + * @param[in, out] state The object that stores translator's internal information. The primary + * state member variable is always updated if a transition occures. Even if there's no state + * transition, other analysis info may be updated. + * @param[in, out] it The iterator that represents the current regex string scan position. May be + * updated to advance or backtrack the scan position. + * @param[out] wildcard_str The translated wildcard string. May or may not be updated. + * @param[in] config The translator config predefined by the user. + * @return clp::regex_utils::ErrorCode + */ +using StateTransitionFuncSig + = auto(TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config) -> error_code; + +/** + * Treats each character literally and directly append it to the wildcard string, unless it is a + * meta-character. + * + * Each meta-character either triggers a state transition, or makes the regex string untranslatable. + */ +[[nodiscard]] StateTransitionFuncSig normal_state_transition; + +/** + * Attempts to translate regex wildcard patterns that start with `.` character. + * + * Performs the following translation if possible: + *
    + *
  • `.*` gets translated into `*`
  • + *
  • `.+` gets translated into `?*`
  • + *
  • `.` gets translated into `?`
  • + *
+ */ +[[nodiscard]] StateTransitionFuncSig dot_state_transition; + +/** + * Appends an escaped regex metacharacter as a literal character to the wildcard string by + * discarding its preceding backslash. + * + * The preceding backslash must be kept for characters that also have special meanings in the + * wildcard syntax, e.g. `abc.\*xyz` should be translated into `abc?\*xyz` instead of `abc?*xyz`. + */ +[[nodiscard]] StateTransitionFuncSig escaped_state_transition; + +/** + * Reduces a regex character set into a single character so that the regex string is still + * translatable into a wildcard string. + * + * In most cases, only a trival character set containing a single character is reducable. However, + * if the output wildcard query will be analyzed in case-insensitive mode, character set patterns + * such as [aA] [Bb] are also reducable. Does not support empty charsets. + * On error, returns IncompleteCharsetStructure, UnsupportedCharsetPattern, or IllegalState. + */ +[[nodiscard]] StateTransitionFuncSig charset_state_transition; + +/** + * A transient state used to defer handling of escape sequences in a charset pattern. + * + * Allows the charset state to accurately capture the appearance of a closing bracket `]`. + */ +[[nodiscard]] StateTransitionFuncSig charset_escaped_state_transition; + +/** + * Disallows the appearances of other characters after encountering an end anchor in the string. + */ +[[nodiscard]] StateTransitionFuncSig end_state_transition; + +/** + * States other than the Normal state may require special handling after the whole regex string has + * been scanned and processed. + */ +[[nodiscard]] StateTransitionFuncSig final_state_cleanup; + +/** + * Appends a single character as a literal to the wildcard string. + * + * If the literal is a metacharacter in the wildcard syntax, prepend the literal with an escape + * backslash. + * @param ch The literal to be appended. + * @param wildcard_str The wildcard string to be updated. + */ +auto append_char_to_wildcard(char ch, string& wildcard_str) -> void; + +/** + * @param ch0 + * @param ch1 + * @return Whether the given chars are the same, but with opposing letter cases, e.g. 'A' vs. 'a'. + */ +[[nodiscard]] auto is_same_char_opposite_case(char ch0, char ch1) -> bool; + +auto normal_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const ch{*it}; + switch (ch) { + case '.': + state.set_next_state(TranslatorState::RegexPatternState::Dot); + break; + case cEscapeChar: + state.set_next_state(TranslatorState::RegexPatternState::Escaped); + break; + case '[': + state.set_charset_begin_it(it + 1); + state.set_next_state(TranslatorState::RegexPatternState::Charset); + break; + case cRegexEndAnchor: + state.set_next_state(TranslatorState::RegexPatternState::End); + break; + case cRegexZeroOrMore: + return ErrorCode::UntranslatableStar; + case cRegexOneOrMore: + return ErrorCode::UntranslatablePlus; + case cRegexZeroOrOne: + return ErrorCode::UnsupportedQuestionMark; + case '|': + return ErrorCode::UnsupportedPipe; + case cRegexStartAnchor: + return ErrorCode::IllegalCaret; + case ')': + return ErrorCode::UnmatchedParenthesis; + default: + wildcard_str += ch; + break; + } + return ErrorCode::Success; +} + +auto dot_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + switch (*it) { + case cZeroOrMoreCharsWildcard: + wildcard_str += cZeroOrMoreCharsWildcard; + break; + case cRegexOneOrMore: + wildcard_str = wildcard_str + cSingleCharWildcard + cZeroOrMoreCharsWildcard; + break; + default: + wildcard_str += cSingleCharWildcard; + // Backtrack the scan by one position to handle the current char in the next iteration. + --it; + break; + } + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto escaped_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const ch{*it}; + if (false == cRegexEscapeSeqMetaCharsLut.at(ch)) { + return ErrorCode::IllegalEscapeSequence; + } + append_char_to_wildcard(ch, wildcard_str); + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto charset_state_transition( + TranslatorState& state, + string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config +) -> error_code { + auto const charset_begin_it_opt{state.get_charset_begin_it()}; + if (false == charset_begin_it_opt.has_value()) { + return ErrorCode::IllegalState; + } + string_view::const_iterator const charset_begin_it = charset_begin_it_opt.value(); + + auto const ch{*it}; + if (cEscapeChar == ch) { + state.set_next_state(TranslatorState::RegexPatternState::CharsetEscaped); + return ErrorCode::Success; + } + if (']' != ch) { + return ErrorCode::Success; + } + + auto const charset_len{it - charset_begin_it}; + if (0 == charset_len || charset_len > 2) { + return ErrorCode::UnsupportedCharsetPattern; + } + + auto const ch0{*charset_begin_it}; + auto const ch1{*(charset_begin_it + 1)}; + char parsed_char{}; + + if (1 == charset_len) { + if (cCharsetNegate == ch0 || cEscapeChar == ch0) { + return ErrorCode::UnsupportedCharsetPattern; + } + parsed_char = ch0; + } else { // 2 == charset_len + if (cEscapeChar == ch0 && cRegexCharsetEscapeSeqMetaCharsLut.at(ch1)) { + parsed_char = ch1; + } else if (config.case_insensitive_wildcard() && is_same_char_opposite_case(ch0, ch1)) { + parsed_char = ch0 > ch1 ? ch0 : ch1; // choose the lower case character + } else { + return ErrorCode::UnsupportedCharsetPattern; + } + } + + append_char_to_wildcard(parsed_char, wildcard_str); + state.invalidate_charset_begin_it(); + state.set_next_state(TranslatorState::RegexPatternState::Normal); + return ErrorCode::Success; +} + +auto charset_escaped_state_transition( + TranslatorState& state, + [[maybe_unused]] string_view::const_iterator& it, + [[maybe_unused]] string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + state.set_next_state(TranslatorState::RegexPatternState::Charset); + return ErrorCode::Success; +} + +auto end_state_transition( + [[maybe_unused]] TranslatorState& state, + string_view::const_iterator& it, + [[maybe_unused]] string& wildcard_str, + [[maybe_unused]] RegexToWildcardTranslatorConfig const& config +) -> error_code { + if (cRegexEndAnchor != *it) { + return ErrorCode::IllegalDollarSign; + } + return ErrorCode::Success; +} + +auto final_state_cleanup( + TranslatorState& state, + [[maybe_unused]] string_view::const_iterator& it, + string& wildcard_str, + RegexToWildcardTranslatorConfig const& config +) -> error_code { + switch (state.get_state()) { + case TranslatorState::RegexPatternState::Dot: + // The last character is a single `.`, without the possibility of becoming a + // multichar wildcard + wildcard_str += cSingleCharWildcard; + break; + case TranslatorState::RegexPatternState::Charset: + case TranslatorState::RegexPatternState::CharsetEscaped: + return ErrorCode::IncompleteCharsetStructure; + default: + break; + } + + if (TranslatorState::RegexPatternState::End != state.get_state() + && config.add_prefix_suffix_wildcards()) + { + wildcard_str += cZeroOrMoreCharsWildcard; + } + return ErrorCode::Success; +} + +auto append_char_to_wildcard(char ch, string& wildcard_str) -> void { + if (cWildcardMetaCharsLut.at(ch)) { + wildcard_str += cEscapeChar; + } + wildcard_str += ch; +} + +auto is_same_char_opposite_case(char ch0, char ch1) -> bool { + int const upper_lower_case_ascii_offset{'a' - 'A'}; + return (is_alphabet(ch0) && is_alphabet(ch1) + && (((ch0 - ch1) == upper_lower_case_ascii_offset) + || ((ch1 - ch0) == upper_lower_case_ascii_offset))); +} +} // namespace + +auto regex_to_wildcard(string_view regex_str) -> OUTCOME_V2_NAMESPACE::std_result { + return regex_to_wildcard( + regex_str, + {/*case_insensitive_wildcard=*/false, /*add_prefix_suffix_wildcards=*/false} + ); +} + +auto regex_to_wildcard(string_view regex_str, RegexToWildcardTranslatorConfig const& config) + -> OUTCOME_V2_NAMESPACE::std_result { + if (regex_str.empty()) { + return string{}; + } + + string_view::const_iterator it{regex_str.cbegin()}; + string wildcard_str; + TranslatorState state; + + // If there is no starting anchor character, append multichar wildcard prefix + if (cRegexStartAnchor == *it) { + ++it; + } else if (config.add_prefix_suffix_wildcards()) { + wildcard_str += cZeroOrMoreCharsWildcard; + } + + error_code ec{}; + while (it != regex_str.cend()) { + switch (state.get_state()) { + case TranslatorState::RegexPatternState::Normal: + ec = normal_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Dot: + ec = dot_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Escaped: + ec = escaped_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::Charset: + ec = charset_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::CharsetEscaped: + ec = charset_escaped_state_transition(state, it, wildcard_str, config); + break; + case TranslatorState::RegexPatternState::End: + ec = end_state_transition(state, it, wildcard_str, config); + break; + default: + ec = ErrorCode::IllegalState; + break; + } + if (ec) { + return ec; + } + ++it; + } + + ec = final_state_cleanup(state, it, wildcard_str, config); + if (ec) { + return ec; + } + return wildcard_str; +} +} // namespace clp::regex_utils diff --git a/components/core/src/clp/regex_utils/regex_translation_utils.hpp b/components/core/src/clp/regex_utils/regex_translation_utils.hpp new file mode 100644 index 000000000..8ca703403 --- /dev/null +++ b/components/core/src/clp/regex_utils/regex_translation_utils.hpp @@ -0,0 +1,36 @@ +#ifndef CLP_REGEX_UTILS_REGEX_UTILS_HPP +#define CLP_REGEX_UTILS_REGEX_UTILS_HPP + +#include +#include + +#include + +#include "regex_utils/RegexToWildcardTranslatorConfig.hpp" + +namespace clp::regex_utils { + +/** + * Translate a given regex string to wildcard with the default configuration that has all the + * options set to false. + * + * @param regex_str The regex string to be translated. + * @return The translated wildcard string. + */ +[[nodiscard]] auto regex_to_wildcard(std::string_view regex_str +) -> OUTCOME_V2_NAMESPACE::std_result; + +/** + * Translate a given regex string to wildcard with a custom configuration. + * + * @param regex_str The regex string to be translated. + * @return The translated wildcard string. + */ +[[nodiscard]] auto regex_to_wildcard( + std::string_view regex_str, + RegexToWildcardTranslatorConfig const& config +) -> OUTCOME_V2_NAMESPACE::std_result; + +} // namespace clp::regex_utils + +#endif // CLP_REGEX_UTILS_REGEX_UTILS_HPP diff --git a/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp b/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp index 7b40022a9..9cf578eb8 100644 --- a/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp +++ b/components/core/src/clp/streaming_archive/ArchiveMetadata.cpp @@ -26,6 +26,7 @@ ArchiveMetadata::ArchiveMetadata(FileReader& file_reader) { file_reader.read_numeric_value(m_archive_format_version, false); file_reader.read_numeric_value(m_creator_id_len, false); file_reader.read_string(m_creator_id_len, m_creator_id, false); + file_reader.read_numeric_value(m_creation_idx, false); file_reader.read_numeric_value(m_uncompressed_size, false); file_reader.read_numeric_value(m_compressed_size, false); file_reader.read_numeric_value(m_begin_timestamp, false); diff --git a/components/core/src/clp/streaming_archive/Constants.hpp b/components/core/src/clp/streaming_archive/Constants.hpp index 9e0d60a42..80fe3b1aa 100644 --- a/components/core/src/clp/streaming_archive/Constants.hpp +++ b/components/core/src/clp/streaming_archive/Constants.hpp @@ -4,7 +4,7 @@ #include "../Defs.h" namespace clp::streaming_archive { -constexpr archive_format_version_t cArchiveFormatVersion = cArchiveFormatDevVersionFlag | 8; +constexpr archive_format_version_t cArchiveFormatVersion = cArchiveFormatDevVersionFlag | 9; constexpr char cSegmentsDirname[] = "s"; constexpr char cSegmentListFilename[] = "segment_list.txt"; constexpr char cLogTypeDictFilename[] = "logtype.dict"; diff --git a/components/core/src/clp/streaming_archive/reader/Archive.cpp b/components/core/src/clp/streaming_archive/reader/Archive.cpp index a836a3785..05e42cac3 100644 --- a/components/core/src/clp/streaming_archive/reader/Archive.cpp +++ b/components/core/src/clp/streaming_archive/reader/Archive.cpp @@ -168,30 +168,14 @@ bool Archive::decompress_message( Message const& compressed_msg, string& decompressed_msg ) { - decompressed_msg.clear(); - - // Build original message content - logtype_dictionary_id_t const logtype_id = compressed_msg.get_logtype_id(); - auto const& logtype_entry = m_logtype_dictionary.get_entry(logtype_id); - if (!EncodedVariableInterpreter::decode_variables_into_message( - logtype_entry, - m_var_dictionary, - compressed_msg.get_vars(), - decompressed_msg - )) - { - SPDLOG_ERROR( - "streaming_archive::reader::Archive: Failed to decompress variables from " - "logtype id {}", - compressed_msg.get_logtype_id() - ); + if (false == decompress_message_without_ts(compressed_msg, decompressed_msg)) { return false; } // Determine which timestamp pattern to use auto const& timestamp_patterns = file.get_timestamp_patterns(); if (!timestamp_patterns.empty() - && compressed_msg.get_message_number() + && compressed_msg.get_ix_in_file_split() >= timestamp_patterns[file.get_current_ts_pattern_ix()].first) { while (true) { @@ -201,7 +185,7 @@ bool Archive::decompress_message( } auto next_patt_start_message_num = timestamp_patterns[file.get_current_ts_pattern_ix() + 1].first; - if (compressed_msg.get_message_number() < next_patt_start_message_num) { + if (compressed_msg.get_ix_in_file_split() < next_patt_start_message_num) { // Not yet time for next timestamp pattern break; } @@ -216,6 +200,34 @@ bool Archive::decompress_message( return true; } +bool Archive::decompress_message_without_ts( + Message const& compressed_msg, + string& decompressed_msg +) { + decompressed_msg.clear(); + + // Build original message content + auto const logtype_id = compressed_msg.get_logtype_id(); + auto const& logtype_entry = m_logtype_dictionary.get_entry(logtype_id); + if (false + == EncodedVariableInterpreter::decode_variables_into_message( + logtype_entry, + m_var_dictionary, + compressed_msg.get_vars(), + decompressed_msg + )) + { + SPDLOG_ERROR( + "streaming_archive::reader::Archive: Failed to decompress variables from " + "logtype id {}", + compressed_msg.get_logtype_id() + ); + return false; + } + + return true; +} + void Archive::decompress_empty_directories(string const& output_dir) { boost::filesystem::path output_dir_path = boost::filesystem::path(output_dir); diff --git a/components/core/src/clp/streaming_archive/reader/Archive.hpp b/components/core/src/clp/streaming_archive/reader/Archive.hpp index 14792670e..c724be476 100644 --- a/components/core/src/clp/streaming_archive/reader/Archive.hpp +++ b/components/core/src/clp/streaming_archive/reader/Archive.hpp @@ -89,7 +89,8 @@ class Archive { bool get_next_message(File& file, Message& msg); /** - * Decompresses a given message from a given file + * Decompresses the given message from the given file, including inserting and formatting its + * timestamp if necessary. * @param file * @param compressed_msg * @param decompressed_msg @@ -99,6 +100,15 @@ class Archive { bool decompress_message(File& file, Message const& compressed_msg, std::string& decompressed_msg); + /** + * Decompresses the given message without inserting its timestamp. + * @param compressed_msg + * @param decompressed_msg + * @return Whether the message was successfully decompressed + */ + bool + decompress_message_without_ts(Message const& compressed_msg, std::string& decompressed_msg); + void decompress_empty_directories(std::string const& output_dir); std::unique_ptr get_file_iterator_by_split_id( diff --git a/components/core/src/clp/streaming_archive/reader/File.cpp b/components/core/src/clp/streaming_archive/reader/File.cpp index 916d7963a..7de4ccbec 100644 --- a/components/core/src/clp/streaming_archive/reader/File.cpp +++ b/components/core/src/clp/streaming_archive/reader/File.cpp @@ -236,7 +236,7 @@ bool File::find_message_in_time_range( // Set remaining message properties msg.set_logtype_id(logtype_id); msg.set_timestamp(timestamp); - msg.set_message_number(m_msgs_ix); + msg.set_msg_ix(m_begin_message_ix, m_msgs_ix); found_msg = true; } @@ -279,7 +279,7 @@ SubQuery const* File::find_message_matching_query(Query const& query, Message& m msg.set_logtype_id(logtype_id); msg.set_timestamp(timestamp); - msg.set_message_number(m_msgs_ix); + msg.set_msg_ix(m_begin_message_ix, m_msgs_ix); matching_sub_query = sub_query; break; } @@ -298,7 +298,7 @@ bool File::get_next_message(Message& msg) { } // Get message number - msg.set_message_number(m_msgs_ix); + msg.set_msg_ix(m_begin_message_ix, m_msgs_ix); // Get timestamp msg.set_timestamp(m_timestamps[m_msgs_ix]); diff --git a/components/core/src/clp/streaming_archive/reader/Message.cpp b/components/core/src/clp/streaming_archive/reader/Message.cpp index 706ed4191..0e043d775 100644 --- a/components/core/src/clp/streaming_archive/reader/Message.cpp +++ b/components/core/src/clp/streaming_archive/reader/Message.cpp @@ -1,8 +1,12 @@ #include "Message.hpp" namespace clp::streaming_archive::reader { -size_t Message::get_message_number() const { - return m_message_number; +auto Message::get_log_event_ix() const -> size_t { + return m_log_event_ix; +} + +auto Message::get_ix_in_file_split() const -> size_t { + return m_ix_in_file_split; } logtype_dictionary_id_t Message::get_logtype_id() const { @@ -17,8 +21,9 @@ epochtime_t Message::get_ts_in_milli() const { return m_timestamp; } -void Message::set_message_number(uint64_t message_number) { - m_message_number = message_number; +auto Message::set_msg_ix(uint64_t file_split_begin_msg_ix, uint64_t msg_ix_in_file_split) -> void { + m_ix_in_file_split = msg_ix_in_file_split; + m_log_event_ix = m_ix_in_file_split + file_split_begin_msg_ix; } void Message::set_logtype_id(logtype_dictionary_id_t logtype_id) { diff --git a/components/core/src/clp/streaming_archive/reader/Message.hpp b/components/core/src/clp/streaming_archive/reader/Message.hpp index 52069ea47..fc7f3ec77 100644 --- a/components/core/src/clp/streaming_archive/reader/Message.hpp +++ b/components/core/src/clp/streaming_archive/reader/Message.hpp @@ -10,12 +10,13 @@ namespace clp::streaming_archive::reader { class Message { public: // Methods - size_t get_message_number() const; + auto get_log_event_ix() const -> size_t; + auto get_ix_in_file_split() const -> size_t; logtype_dictionary_id_t get_logtype_id() const; std::vector const& get_vars() const; epochtime_t get_ts_in_milli() const; - void set_message_number(uint64_t message_number); + auto set_msg_ix(uint64_t file_split_begin_msg_ix, uint64_t msg_ix_in_file_split) -> void; void set_logtype_id(logtype_dictionary_id_t logtype_id); void add_var(encoded_variable_t var); void set_timestamp(epochtime_t timestamp); @@ -26,7 +27,8 @@ class Message { friend class Archive; // Variables - size_t m_message_number; + size_t m_log_event_ix; + size_t m_ix_in_file_split; logtype_dictionary_id_t m_logtype_id; std::vector m_vars; epochtime_t m_timestamp; diff --git a/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp b/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp index 9f320efe6..818379a24 100644 --- a/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp +++ b/components/core/src/clp/streaming_compression/zstd/Decompressor.cpp @@ -2,9 +2,8 @@ #include -#include - #include "../../Defs.h" +#include "../../ReadOnlyMemoryMappedFile.hpp" #include "../../spdlog_with_specializations.hpp" namespace clp::streaming_compression::zstd { @@ -182,10 +181,7 @@ void Decompressor::open(FileReader& file_reader, size_t file_read_buffer_capacit void Decompressor::close() { switch (m_input_type) { case InputType::MemoryMappedCompressedFile: - if (m_memory_mapped_compressed_file.is_open()) { - // An existing file is memory mapped by the decompressor - m_memory_mapped_compressed_file.close(); - } + m_memory_mapped_file.reset(); break; case InputType::File: m_file_read_buffer.reset(); @@ -209,40 +205,12 @@ ErrorCode Decompressor::open(std::string const& compressed_file_path) { } m_input_type = InputType::MemoryMappedCompressedFile; - // Create memory mapping for compressed_file_path, use boost read only - // memory mapped file - boost::system::error_code boost_error_code; - size_t compressed_file_size - = boost::filesystem::file_size(compressed_file_path, boost_error_code); - if (boost_error_code) { - SPDLOG_ERROR( - "streaming_compression::zstd::Decompressor: Unable to obtain file size for " - "'{}' - {}.", - compressed_file_path.c_str(), - boost_error_code.message().c_str() - ); - return ErrorCode_Failure; - } - - boost::iostreams::mapped_file_params memory_map_params; - memory_map_params.path = compressed_file_path; - memory_map_params.flags = boost::iostreams::mapped_file::readonly; - memory_map_params.length = compressed_file_size; - // Try to map it to the same memory location as previous memory mapped - // file - memory_map_params.hint = m_memory_mapped_compressed_file.data(); - m_memory_mapped_compressed_file.open(memory_map_params); - if (!m_memory_mapped_compressed_file.is_open()) { - SPDLOG_ERROR( - "streaming_compression::zstd::Decompressor: Unable to memory map the " - "compressed file with path: {}", - compressed_file_path.c_str() - ); - return ErrorCode_Failure; - } + // Create read-only memory mapping for compressed_file_path + m_memory_mapped_file = std::make_unique(compressed_file_path); + auto const file_view{m_memory_mapped_file->get_view()}; // Configure input stream - m_compressed_stream_block = {m_memory_mapped_compressed_file.data(), compressed_file_size, 0}; + m_compressed_stream_block = {file_view.data(), file_view.size(), 0}; reset_stream(); diff --git a/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp b/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp index 665674373..cc9e90fe4 100644 --- a/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp +++ b/components/core/src/clp/streaming_compression/zstd/Decompressor.hpp @@ -4,10 +4,10 @@ #include #include -#include #include #include "../../FileReader.hpp" +#include "../../ReadOnlyMemoryMappedFile.hpp" #include "../../TraceableException.hpp" #include "../Decompressor.hpp" @@ -125,7 +125,7 @@ class Decompressor : public ::clp::streaming_compression::Decompressor { // Compressed stream variables ZSTD_DStream* m_decompression_stream; - boost::iostreams::mapped_file_source m_memory_mapped_compressed_file; + std::unique_ptr m_memory_mapped_file; FileReader* m_file_reader; size_t m_file_reader_initial_pos; std::unique_ptr m_file_read_buffer; diff --git a/components/core/src/clp/utf8_utils.cpp b/components/core/src/clp/utf8_utils.cpp new file mode 100644 index 000000000..06fafd659 --- /dev/null +++ b/components/core/src/clp/utf8_utils.cpp @@ -0,0 +1,55 @@ +#include "utf8_utils.hpp" + +#include +#include +#include + +namespace clp { +auto is_utf8_encoded(std::string_view str) -> bool { + auto escape_handler = []([[maybe_unused]] std::string_view::const_iterator it) -> void {}; + return validate_utf8_string(str, escape_handler); +} + +namespace utf8_utils_internal { +auto parse_and_validate_lead_byte( + uint8_t byte, + size_t& num_continuation_bytes, + uint32_t& code_point, + uint32_t& code_point_lower_bound, + uint32_t& code_point_upper_bound +) -> bool { + if ((byte & cFourByteUtf8CharHeaderMask) == cFourByteUtf8CharHeader) { + num_continuation_bytes = 3; + code_point = (~cFourByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cFourByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cFourByteUtf8CharCodePointUpperBound; + } else if ((byte & cThreeByteUtf8CharHeaderMask) == cThreeByteUtf8CharHeader) { + num_continuation_bytes = 2; + code_point = (~cThreeByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cThreeByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cThreeByteUtf8CharCodePointUpperBound; + } else if ((byte & cTwoByteUtf8CharHeaderMask) == cTwoByteUtf8CharHeader) { + num_continuation_bytes = 1; + code_point = (~cTwoByteUtf8CharHeaderMask & byte); + code_point_lower_bound = cTwoByteUtf8CharCodePointLowerBound; + code_point_upper_bound = cTwoByteUtf8CharCodePointUpperBound; + } else { + return false; + } + return true; +} + +auto is_ascii_char(uint8_t byte) -> bool { + return cOneByteUtf8CharCodePointUpperBound >= byte; +} + +auto is_valid_utf8_continuation_byte(uint8_t byte) -> bool { + return (byte & cUtf8ContinuationByteMask) == cUtf8ContinuationByteHeader; +} + +auto parse_continuation_byte(uint32_t code_point, uint8_t continuation_byte) -> uint32_t { + return (code_point << cUtf8NumContinuationByteCodePointBits) + + (continuation_byte & cUtf8ContinuationByteCodePointMask); +} +} // namespace utf8_utils_internal +} // namespace clp diff --git a/components/core/src/clp/utf8_utils.hpp b/components/core/src/clp/utf8_utils.hpp new file mode 100644 index 000000000..fe9569b00 --- /dev/null +++ b/components/core/src/clp/utf8_utils.hpp @@ -0,0 +1,144 @@ +#ifndef CLP_UTF8_UTILS_HPP +#define CLP_UTF8_UTILS_HPP + +#include +#include +#include + +namespace clp { +// Constants +// Lead byte signature +constexpr uint8_t cTwoByteUtf8CharHeaderMask{0xE0}; // 0b111x_xxxx +constexpr uint8_t cTwoByteUtf8CharHeader{0xC0}; // 0b110x_xxxx +constexpr uint8_t cThreeByteUtf8CharHeaderMask{0xF0}; // 0b1111_xxxx +constexpr uint8_t cThreeByteUtf8CharHeader{0xE0}; // 0b1110_xxxx +constexpr uint8_t cFourByteUtf8CharHeaderMask{0xF8}; // 0b1111_1xxx +constexpr uint8_t cFourByteUtf8CharHeader{0xF0}; // 0b1111_0xxx + +// Code point ranges (inclusive) +constexpr uint32_t cOneByteUtf8CharCodePointLowerBound{0}; +constexpr uint32_t cOneByteUtf8CharCodePointUpperBound{0x7F}; +constexpr uint32_t cTwoByteUtf8CharCodePointLowerBound{0x80}; +constexpr uint32_t cTwoByteUtf8CharCodePointUpperBound{0x7FF}; +constexpr uint32_t cThreeByteUtf8CharCodePointLowerBound{0x800}; +constexpr uint32_t cThreeByteUtf8CharCodePointUpperBound{0xFFFF}; +constexpr uint32_t cFourByteUtf8CharCodePointLowerBound{0x1'0000}; +constexpr uint32_t cFourByteUtf8CharCodePointUpperBound{0x10'FFFF}; + +// Continuation byte +constexpr uint32_t cUtf8ContinuationByteMask{0xC0}; +constexpr uint32_t cUtf8ContinuationByteHeader{0x80}; +constexpr uint32_t cUtf8ContinuationByteCodePointMask{0x3F}; +constexpr uint8_t cUtf8NumContinuationByteCodePointBits{6}; + +/** + * Validates whether the given string is UTF-8 encoded, optionally escaping ASCII characters using + * the given handler. + * @tparam EscapeHandler Method to optionally escape any ASCII character in the string. + * @param src + * @param escape_handler + * @return Whether the input is a valid UTF-8 encoded string. + */ +template +requires std::is_invocable_v +[[nodiscard]] auto validate_utf8_string(std::string_view src, EscapeHandler escape_handler) -> bool; + +/** + * @param str + * @return Whether the input is a valid UTF-8 encoded string. + */ +[[nodiscard]] auto is_utf8_encoded(std::string_view str) -> bool; + +namespace utf8_utils_internal { +/** + * Validates whether the given byte is a valid lead byte for a multi-byte UTF-8 character, parses + * the byte, and returns the parsed properties as well as associated properties. + * @param byte Byte to validate. + * @param num_continuation_bytes Returns the number of continuation bytes expected. + * @param code_point Returns the code point bits parsed from the lead byte. + * @param code_point_lower_bound Returns the lower bound of the code point range for the UTF-8 + * character. + * @param code_point_upper_bound Returns the upper bound of the code point range for the UTF-8 + * character. + * @return Whether the input byte is a valid lead byte for a multi-byte UTF-8 character. + */ +[[nodiscard]] auto parse_and_validate_lead_byte( + uint8_t byte, + size_t& num_continuation_bytes, + uint32_t& code_point, + uint32_t& code_point_lower_bound, + uint32_t& code_point_upper_bound +) -> bool; + +/** + * @param byte + * @return Whether the given byte is a valid ASCII character. + */ +[[nodiscard]] auto is_ascii_char(uint8_t byte) -> bool; + +/* + * @param byte + * @return Whether the input byte is a valid UTF-8 continuation byte. + */ +[[nodiscard]] auto is_valid_utf8_continuation_byte(uint8_t byte) -> bool; + +/** + * Parses the code-point bits from the given continuation byte and combines them with the given + * code point. + * @param code_point + * @param continuation_byte + * @return The updated code point. + */ +[[nodiscard]] auto +parse_continuation_byte(uint32_t code_point, uint8_t continuation_byte) -> uint32_t; +} // namespace utf8_utils_internal + +template +requires std::is_invocable_v +auto validate_utf8_string(std::string_view src, EscapeHandler escape_handler) -> bool { + size_t num_continuation_bytes_to_validate{0}; + uint32_t code_point{}; + uint32_t code_point_lower_bound{}; + uint32_t code_point_upper_bound{}; + + // NOLINTNEXTLINE(readability-qualified-auto) + for (auto it{src.cbegin()}; it != src.cend(); ++it) { + auto const byte{static_cast(*it)}; + if (0 == num_continuation_bytes_to_validate) { + if (utf8_utils_internal::is_ascii_char(byte)) { + escape_handler(it); + } else if (false + == utf8_utils_internal::parse_and_validate_lead_byte( + byte, + num_continuation_bytes_to_validate, + code_point, + code_point_lower_bound, + code_point_upper_bound + )) + { + return false; + } + } else { + if (false == utf8_utils_internal::is_valid_utf8_continuation_byte(byte)) { + return false; + } + code_point = utf8_utils_internal::parse_continuation_byte(code_point, byte); + --num_continuation_bytes_to_validate; + if (0 == num_continuation_bytes_to_validate + && (code_point < code_point_lower_bound || code_point_upper_bound < code_point)) + { + return false; + } + } + } + + if (0 != num_continuation_bytes_to_validate) { + // Incomplete UTF-8 character + return false; + } + + return true; +} +} // namespace clp + +#endif // CLP_UTF8_UTILS_HPP diff --git a/components/core/src/clp_s/ArchiveReader.cpp b/components/core/src/clp_s/ArchiveReader.cpp index 93f905e3b..f211a0707 100644 --- a/components/core/src/clp_s/ArchiveReader.cpp +++ b/components/core/src/clp_s/ArchiveReader.cpp @@ -1,26 +1,34 @@ #include "ArchiveReader.hpp" +#include +#include + #include "archive_constants.hpp" #include "ReaderUtils.hpp" +using std::string_view; + namespace clp_s { -void ArchiveReader::open(std::string const& archive_path) { +void ArchiveReader::open(string_view archives_dir, string_view archive_id) { if (m_is_open) { throw OperationFailed(ErrorCodeNotReady, __FILENAME__, __LINE__); } m_is_open = true; - m_archive_path = archive_path; + m_archive_id = archive_id; + std::filesystem::path archive_path{archives_dir}; + archive_path /= m_archive_id; + auto const archive_path_str = archive_path.string(); - m_var_dict = ReaderUtils::get_variable_dictionary_reader(m_archive_path); - m_log_dict = ReaderUtils::get_log_type_dictionary_reader(m_archive_path); - m_array_dict = ReaderUtils::get_array_dictionary_reader(m_archive_path); - m_timestamp_dict = ReaderUtils::get_timestamp_dictionary_reader(m_archive_path); + m_var_dict = ReaderUtils::get_variable_dictionary_reader(archive_path_str); + m_log_dict = ReaderUtils::get_log_type_dictionary_reader(archive_path_str); + m_array_dict = ReaderUtils::get_array_dictionary_reader(archive_path_str); + m_timestamp_dict = ReaderUtils::get_timestamp_dictionary_reader(archive_path_str); - m_schema_tree = ReaderUtils::read_schema_tree(m_archive_path); - m_schema_map = ReaderUtils::read_schemas(m_archive_path); + m_schema_tree = ReaderUtils::read_schema_tree(archive_path_str); + m_schema_map = ReaderUtils::read_schemas(archive_path_str); - m_tables_file_reader.open(m_archive_path + constants::cArchiveTablesFile); - m_table_metadata_file_reader.open(m_archive_path + constants::cArchiveTableMetadataFile); + m_tables_file_reader.open(archive_path_str + constants::cArchiveTablesFile); + m_table_metadata_file_reader.open(archive_path_str + constants::cArchiveTableMetadataFile); } void ArchiveReader::read_metadata() { @@ -92,14 +100,40 @@ SchemaReader& ArchiveReader::read_table( throw OperationFailed(ErrorCodeFileNotFound, __FILENAME__, __LINE__); } - auto& schema_reader - = create_schema_reader(schema_id, should_extract_timestamp, should_marshal_records); + initialize_schema_reader( + m_schema_reader, + schema_id, + should_extract_timestamp, + should_marshal_records + ); m_tables_file_reader.try_seek_from_begin(m_id_to_table_metadata[schema_id].offset); m_tables_decompressor.open(m_tables_file_reader, cDecompressorFileReadBufferCapacity); - schema_reader.load(m_tables_decompressor, m_id_to_table_metadata[schema_id].uncompressed_size); + m_schema_reader.load( + m_tables_decompressor, + m_id_to_table_metadata[schema_id].uncompressed_size + ); m_tables_decompressor.close_for_reuse(); - return schema_reader; + return m_schema_reader; +} + +std::vector> ArchiveReader::read_all_tables() { + constexpr size_t cDecompressorFileReadBufferCapacity = 64 * 1024; // 64 KB + + std::vector> readers; + readers.reserve(m_id_to_table_metadata.size()); + for (auto const& [id, table_metadata] : m_id_to_table_metadata) { + auto schema_reader = std::make_shared(); + initialize_schema_reader(*schema_reader, id, true, true); + + m_tables_file_reader.try_seek_from_begin(table_metadata.offset); + m_tables_decompressor.open(m_tables_file_reader, cDecompressorFileReadBufferCapacity); + schema_reader->load(m_tables_decompressor, table_metadata.uncompressed_size); + m_tables_decompressor.close_for_reuse(); + + readers.push_back(std::move(schema_reader)); + } + return readers; } BaseColumnReader* ArchiveReader::append_reader_column(SchemaReader& reader, int32_t column_id) { @@ -192,13 +226,14 @@ void ArchiveReader::append_unordered_reader_columns( } } -SchemaReader& ArchiveReader::create_schema_reader( +void ArchiveReader::initialize_schema_reader( + SchemaReader& reader, int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records ) { auto& schema = (*m_schema_map)[schema_id]; - m_schema_reader.reset( + reader.reset( m_schema_tree, schema_id, schema.get_ordered_schema_view(), @@ -218,7 +253,7 @@ SchemaReader& ArchiveReader::create_schema_reader( Schema::get_unordered_object_type(column_id) ); append_unordered_reader_columns( - m_schema_reader, + reader, mst_subtree_root_node_id, sub_schema, should_marshal_records @@ -231,21 +266,20 @@ SchemaReader& ArchiveReader::create_schema_reader( // column id is the root of the unordered object, so we can pass it directly to // append_unordered_reader_columns. append_unordered_reader_columns( - m_schema_reader, + reader, column_id, std::span(), should_marshal_records ); continue; } - BaseColumnReader* column_reader = append_reader_column(m_schema_reader, column_id); + BaseColumnReader* column_reader = append_reader_column(reader, column_id); if (should_extract_timestamp && column_reader && timestamp_column_ids.count(column_id) > 0) { - m_schema_reader.mark_column_as_timestamp(column_reader); + reader.mark_column_as_timestamp(column_reader); } } - return m_schema_reader; } void ArchiveReader::store(FileWriter& writer) { diff --git a/components/core/src/clp_s/ArchiveReader.hpp b/components/core/src/clp_s/ArchiveReader.hpp index 54eb42698..91fcc1a94 100644 --- a/components/core/src/clp_s/ArchiveReader.hpp +++ b/components/core/src/clp_s/ArchiveReader.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -29,9 +30,10 @@ class ArchiveReader { /** * Opens an archive for reading. - * @param archive_path + * @param archives_dir + * @param archive_id */ - void open(std::string const& archive_path); + void open(std::string_view archives_dir, std::string_view archive_id); /** * Reads the dictionaries and metadata. @@ -92,6 +94,14 @@ class ArchiveReader { SchemaReader& read_table(int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records); + /** + * Loads all of the tables in the archive and returns SchemaReaders for them. + * @return the schema readers for every table in the archive + */ + std::vector> read_all_tables(); + + std::string_view get_archive_id() { return m_archive_id; } + std::shared_ptr get_variable_dictionary() { return m_var_dict; } std::shared_ptr get_log_type_dictionary() { return m_log_dict; } @@ -125,13 +135,14 @@ class ArchiveReader { private: /** - * Creates a schema reader for a given schema. + * Initializes a schema reader passed by reference to become a reader for a given schema. + * @param reader * @param schema_id * @param should_extract_timestamp * @param should_marshal_records - * @return a reference to the newly created schema reader initialized with the given parameters */ - SchemaReader& create_schema_reader( + void initialize_schema_reader( + SchemaReader& reader, int32_t schema_id, bool should_extract_timestamp, bool should_marshal_records @@ -161,8 +172,7 @@ class ArchiveReader { ); bool m_is_open; - std::string m_archive_path; - + std::string m_archive_id; std::shared_ptr m_var_dict; std::shared_ptr m_log_dict; std::shared_ptr m_array_dict; diff --git a/components/core/src/clp_s/CommandLineArguments.cpp b/components/core/src/clp_s/CommandLineArguments.cpp index 640a45f8c..4cfe017ac 100644 --- a/components/core/src/clp_s/CommandLineArguments.cpp +++ b/components/core/src/clp_s/CommandLineArguments.cpp @@ -286,6 +286,36 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { ); extraction_options.add(input_options); + po::options_description decompression_options("Decompression Options"); + // clang-format off + decompression_options.add_options()( + "ordered", + po::bool_switch(&m_ordered_decompression), + "Enable decompression in ascending timestamp order for this archive" + )( + "ordered-chunk-size", + po::value(&m_ordered_chunk_size) + ->default_value(m_ordered_chunk_size), + "Number of records to include in each output file when decompressing records " + "in ascending timestamp order" + ); + // clang-format on + extraction_options.add(decompression_options); + + po::options_description output_metadata_options("Output Metadata Options"); + // clang-format off + output_metadata_options.add_options()( + "mongodb-uri", + po::value(&m_mongodb_uri)->value_name("URI"), + "MongoDB URI for the database to write decompression metadata to" + )( + "mongodb-collection", + po::value(&m_mongodb_collection)->value_name("COLLECTION"), + "MongoDB collection to write decompression metadata to" + ); + // clang-format on + extraction_options.add(output_metadata_options); + po::positional_options_description positional_options; positional_options.add("archives-dir", 1); positional_options.add("output-dir", 1); @@ -316,6 +346,8 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { po::options_description visible_options; visible_options.add(general_options); visible_options.add(input_options); + visible_options.add(decompression_options); + visible_options.add(output_metadata_options); std::cerr << visible_options << std::endl; return ParsingResult::InfoCommand; } @@ -327,6 +359,25 @@ CommandLineArguments::parse_arguments(int argc, char const** argv) { if (m_output_dir.empty()) { throw std::invalid_argument("No output directory specified"); } + + if (0 != m_ordered_chunk_size && false == m_ordered_decompression) { + throw std::invalid_argument("ordered-chunk-size must be used with ordered argument" + ); + } + + // We use xor to check that these arguments are either both specified or both + // unspecified. + if (m_mongodb_uri.empty() ^ m_mongodb_collection.empty()) { + throw std::invalid_argument( + "mongodb-uri and mongodb-collection must both be non-empty" + ); + } + + if (false == m_mongodb_uri.empty() && false == m_ordered_decompression) { + throw std::invalid_argument( + "Recording decompression metadata only supported for ordered decompression" + ); + } } else if ((char)Command::Search == command_input) { std::string archives_dir; std::string query; diff --git a/components/core/src/clp_s/CommandLineArguments.hpp b/components/core/src/clp_s/CommandLineArguments.hpp index 20fa3ec6e..0f3d8c556 100644 --- a/components/core/src/clp_s/CommandLineArguments.hpp +++ b/components/core/src/clp_s/CommandLineArguments.hpp @@ -104,6 +104,10 @@ class CommandLineArguments { bool get_structurize_arrays() const { return m_structurize_arrays; } + bool get_ordered_decompression() const { return m_ordered_decompression; } + + size_t get_ordered_chunk_size() const { return m_ordered_chunk_size; } + private: // Methods /** @@ -167,6 +171,8 @@ class CommandLineArguments { bool m_print_archive_stats{false}; size_t m_max_document_size{512ULL * 1024 * 1024}; // 512 MB bool m_structurize_arrays{false}; + bool m_ordered_decompression{false}; + size_t m_ordered_chunk_size{0}; // Metadata db variables std::optional m_metadata_db_config; diff --git a/components/core/src/clp_s/JsonConstructor.cpp b/components/core/src/clp_s/JsonConstructor.cpp index 777f62ce2..90887f1e5 100644 --- a/components/core/src/clp_s/JsonConstructor.cpp +++ b/components/core/src/clp_s/JsonConstructor.cpp @@ -1,19 +1,23 @@ #include "JsonConstructor.hpp" #include +#include #include #include +#include +#include +#include +#include +#include "archive_constants.hpp" #include "ErrorCode.hpp" #include "ReaderUtils.hpp" #include "SchemaTree.hpp" #include "TraceableException.hpp" namespace clp_s { -JsonConstructor::JsonConstructor(JsonConstructorOption const& option) - : m_output_dir(option.output_dir), - m_archives_dir(option.archives_dir) { +JsonConstructor::JsonConstructor(JsonConstructorOption const& option) : m_option{option} { std::error_code error_code; if (false == std::filesystem::create_directory(option.output_dir, error_code) && error_code) { throw OperationFailed( @@ -28,27 +32,150 @@ JsonConstructor::JsonConstructor(JsonConstructorOption const& option) ); } - if (false == std::filesystem::is_directory(m_archives_dir)) { + std::filesystem::path archive_path{m_option.archives_dir}; + archive_path /= m_option.archive_id; + if (false == std::filesystem::is_directory(archive_path)) { throw OperationFailed( ErrorCodeFailure, __FILENAME__, __LINE__, - fmt::format("'{}' is not a directory", m_archives_dir) + fmt::format("'{}' is not a directory", archive_path.c_str()) ); } } void JsonConstructor::store() { - FileWriter writer; - writer.open(m_output_dir + "/original", FileWriter::OpenMode::CreateIfNonexistentForAppending); - m_archive_reader = std::make_unique(); - m_archive_reader->open(m_archives_dir); + m_archive_reader->open(m_option.archives_dir, m_option.archive_id); m_archive_reader->read_dictionaries_and_metadata(); - m_archive_reader->store(writer); - m_archive_reader->close(); + if (false == m_option.ordered) { + FileWriter writer; + writer.open( + m_option.output_dir + "/original", + FileWriter::OpenMode::CreateIfNonexistentForAppending + ); + m_archive_reader->store(writer); - writer.close(); + writer.close(); + } else { + construct_in_order(); + } + m_archive_reader->close(); } +void JsonConstructor::construct_in_order() { + std::string buffer; + auto tables = m_archive_reader->read_all_tables(); + using ReaderPointer = std::shared_ptr; + auto cmp = [](ReaderPointer& left, ReaderPointer& right) { + return left->get_next_timestamp() > right->get_next_timestamp(); + }; + std::priority_queue record_queue(tables.begin(), tables.end(), cmp); + // Clear tables vector so that memory gets deallocated after we have marshalled all records for + // a given table + tables.clear(); + + epochtime_t first_timestamp{0}; + epochtime_t last_timestamp{0}; + size_t num_records_marshalled{0}; + auto src_path = std::filesystem::path(m_option.output_dir) / m_option.archive_id; + FileWriter writer; + writer.open(src_path, FileWriter::OpenMode::CreateForWriting); + + mongocxx::client client; + mongocxx::collection collection; + + if (m_option.metadata_db.has_value()) { + try { + auto const mongo_uri{mongocxx::uri(m_option.metadata_db->mongodb_uri)}; + client = mongocxx::client{mongo_uri}; + collection = client[mongo_uri.database()][m_option.metadata_db->mongodb_collection]; + } catch (mongocxx::exception const& e) { + throw OperationFailed(ErrorCodeBadParamDbUri, __FILE__, __LINE__, e.what()); + } + } + + std::vector results; + auto finalize_chunk = [&](bool open_new_writer) { + writer.close(); + std::string new_file_name = src_path.string() + "_" + std::to_string(first_timestamp) + "_" + + std::to_string(last_timestamp) + ".jsonl"; + auto new_file_path = std::filesystem::path(new_file_name); + std::error_code ec; + std::filesystem::rename(src_path, new_file_path, ec); + if (ec) { + throw OperationFailed(ErrorCodeFailure, __FILE__, __LINE__, ec.message()); + } + + if (m_option.metadata_db.has_value()) { + results.emplace_back(std::move(bsoncxx::builder::basic::make_document( + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cPath, + new_file_path.filename() + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cOrigFileId, + m_option.archive_id + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cBeginMsgIx, + static_cast(first_timestamp) + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cEndMsgIx, + static_cast(last_timestamp) + ), + bsoncxx::builder::basic::kvp( + constants::results_cache::decompression::cIsLastIrChunk, + false == open_new_writer + ) + ))); + } + + if (open_new_writer) { + writer.open(src_path, FileWriter::OpenMode::CreateForWriting); + } + }; + + while (false == record_queue.empty()) { + ReaderPointer next = record_queue.top(); + record_queue.pop(); + last_timestamp = next->get_next_timestamp(); + if (0 == num_records_marshalled) { + first_timestamp = last_timestamp; + } + next->get_next_message(buffer); + if (false == next->done()) { + record_queue.emplace(std::move(next)); + } + writer.write(buffer.c_str(), buffer.length()); + num_records_marshalled += 1; + + if (0 != m_option.ordered_chunk_size + && num_records_marshalled >= m_option.ordered_chunk_size) + { + finalize_chunk(true); + num_records_marshalled = 0; + } + } + + if (num_records_marshalled > 0) { + finalize_chunk(false); + } else { + writer.close(); + std::error_code ec; + std::filesystem::remove(src_path, ec); + if (ec) { + throw OperationFailed(ErrorCodeFailure, __FILE__, __LINE__, ec.message()); + } + } + + if (false == results.empty()) { + try { + collection.insert_many(results); + } catch (mongocxx::exception const& e) { + throw OperationFailed(ErrorCodeFailureDbBulkWrite, __FILE__, __LINE__, e.what()); + } + } +} } // namespace clp_s diff --git a/components/core/src/clp_s/JsonConstructor.hpp b/components/core/src/clp_s/JsonConstructor.hpp index 8aa35f904..f1f71f9d8 100644 --- a/components/core/src/clp_s/JsonConstructor.hpp +++ b/components/core/src/clp_s/JsonConstructor.hpp @@ -1,6 +1,7 @@ #ifndef CLP_S_JSONCONSTRUCTOR_HPP #define CLP_S_JSONCONSTRUCTOR_HPP +#include #include #include #include @@ -9,14 +10,28 @@ #include "ColumnReader.hpp" #include "DictionaryReader.hpp" #include "ErrorCode.hpp" +#include "FileWriter.hpp" #include "SchemaReader.hpp" #include "SchemaTree.hpp" #include "TraceableException.hpp" namespace clp_s { +struct MetadataDbOption { + MetadataDbOption(std::string const& uri, std::string const& collection) + : mongodb_uri{uri}, + mongodb_collection{collection} {} + + std::string mongodb_uri; + std::string mongodb_collection; +}; + struct JsonConstructorOption { std::string archives_dir; + std::string archive_id; std::string output_dir; + bool ordered{false}; + size_t ordered_chunk_size{0}; + std::optional metadata_db; }; class JsonConstructor { @@ -49,9 +64,13 @@ class JsonConstructor { void store(); private: - std::string m_archives_dir; - std::string m_output_dir; + /** + * Reads all of the tables from m_archive_reader and writes all of the records + * they contain to writer in timestamp order. + */ + void construct_in_order(); + JsonConstructorOption m_option{}; std::unique_ptr m_archive_reader; }; } // namespace clp_s diff --git a/components/core/src/clp_s/JsonFileIterator.cpp b/components/core/src/clp_s/JsonFileIterator.cpp index 5fffcc8f9..ad6d16cd0 100644 --- a/components/core/src/clp_s/JsonFileIterator.cpp +++ b/components/core/src/clp_s/JsonFileIterator.cpp @@ -156,4 +156,14 @@ bool JsonFileIterator::get_json(simdjson::ondemand::document_stream::iterator& i } while (read_new_json()); return false; } + +size_t JsonFileIterator::get_num_bytes_consumed() { + // If there are more documents left in the current buffer account for how much of the + // buffer has been consumed, otherwise report the total number of bytes read so that we + // capture trailing whitespace. + if (m_doc_it != m_stream.end()) { + return m_bytes_read - (m_buf_occupied - m_next_document_position); + } + return m_bytes_read; +} } // namespace clp_s diff --git a/components/core/src/clp_s/JsonFileIterator.hpp b/components/core/src/clp_s/JsonFileIterator.hpp index 51422963a..b8db3f4f2 100644 --- a/components/core/src/clp_s/JsonFileIterator.hpp +++ b/components/core/src/clp_s/JsonFileIterator.hpp @@ -51,6 +51,14 @@ class JsonFileIterator { */ [[nodiscard]] size_t get_num_bytes_read() const { return m_bytes_read; } + /** + * Note: this method can not be const because checking if a simdjson iterator is at the end + * of a document stream is non-const. + * + * @return total number of bytes consumed from the file via get_json + */ + [[nodiscard]] size_t get_num_bytes_consumed(); + /** * @return the last error code encountered when iterating over the json file */ diff --git a/components/core/src/clp_s/JsonParser.cpp b/components/core/src/clp_s/JsonParser.cpp index c4fe7a43e..26ec3d7b4 100644 --- a/components/core/src/clp_s/JsonParser.cpp +++ b/components/core/src/clp_s/JsonParser.cpp @@ -11,9 +11,7 @@ namespace clp_s { JsonParser::JsonParser(JsonParserOption const& option) - : m_archives_dir(option.archives_dir), - m_num_messages(0), - m_compression_level(option.compression_level), + : m_num_messages(0), m_target_encoded_size(option.target_encoded_size), m_max_document_size(option.max_document_size), m_timestamp_key(option.timestamp_key), @@ -30,14 +28,13 @@ JsonParser::JsonParser(JsonParserOption const& option) FileUtils::find_all_files(file_path, m_file_paths); } - ArchiveWriterOption archive_writer_option; - archive_writer_option.archives_dir = m_archives_dir; - archive_writer_option.id = m_generator(); - archive_writer_option.compression_level = option.compression_level; - archive_writer_option.print_archive_stats = option.print_archive_stats; + m_archive_options.archives_dir = option.archives_dir; + m_archive_options.compression_level = option.compression_level; + m_archive_options.print_archive_stats = option.print_archive_stats; + m_archive_options.id = m_generator(); m_archive_writer = std::make_unique(option.metadata_db); - m_archive_writer->open(archive_writer_option); + m_archive_writer->open(m_archive_options); } void JsonParser::parse_obj_in_array(ondemand::object line, int32_t parent_node_id) { @@ -442,7 +439,7 @@ bool JsonParser::parse() { simdjson::ondemand::document_stream::iterator json_it; m_num_messages = 0; - size_t last_num_bytes_read = 0; + size_t last_num_bytes_consumed = 0; while (json_file_iterator.get_json(json_it)) { m_current_schema.clear(); @@ -466,9 +463,11 @@ bool JsonParser::parse() { ->append_message(current_schema_id, m_current_schema, m_current_parsed_message); if (m_archive_writer->get_data_size() >= m_target_encoded_size) { - size_t num_bytes_read = json_file_iterator.get_num_bytes_read(); - m_archive_writer->increment_uncompressed_size(num_bytes_read - last_num_bytes_read); - last_num_bytes_read = num_bytes_read; + size_t num_bytes_read = json_file_iterator.get_num_bytes_consumed(); + m_archive_writer->increment_uncompressed_size( + num_bytes_read - last_num_bytes_consumed + ); + last_num_bytes_consumed = num_bytes_read; split_archive(); } @@ -476,7 +475,7 @@ bool JsonParser::parse() { } m_archive_writer->increment_uncompressed_size( - json_file_iterator.get_num_bytes_read() - last_num_bytes_read + json_file_iterator.get_num_bytes_read() - last_num_bytes_consumed ); if (simdjson::error_code::SUCCESS != json_file_iterator.get_error()) { @@ -505,13 +504,8 @@ void JsonParser::store() { void JsonParser::split_archive() { m_archive_writer->close(); - - ArchiveWriterOption archive_writer_option; - archive_writer_option.archives_dir = m_archives_dir; - archive_writer_option.id = m_generator(); - archive_writer_option.compression_level = m_compression_level; - - m_archive_writer->open(archive_writer_option); + m_archive_options.id = m_generator(); + m_archive_writer->open(m_archive_options); } } // namespace clp_s diff --git a/components/core/src/clp_s/JsonParser.hpp b/components/core/src/clp_s/JsonParser.hpp index a0010e66f..f65f4c4b1 100644 --- a/components/core/src/clp_s/JsonParser.hpp +++ b/components/core/src/clp_s/JsonParser.hpp @@ -93,10 +93,7 @@ class JsonParser { void split_archive(); int m_num_messages; - int m_compression_level; std::vector m_file_paths; - std::string m_archives_dir; - std::string m_schema_tree_path; Schema m_current_schema; ParsedMessage m_current_parsed_message; @@ -106,6 +103,7 @@ class JsonParser { boost::uuids::random_generator m_generator; std::unique_ptr m_archive_writer; + ArchiveWriterOption m_archive_options{}; size_t m_target_encoded_size; size_t m_max_document_size; bool m_structurize_arrays{false}; diff --git a/components/core/src/clp_s/SchemaReader.hpp b/components/core/src/clp_s/SchemaReader.hpp index 8597316a6..3639560f6 100644 --- a/components/core/src/clp_s/SchemaReader.hpp +++ b/components/core/src/clp_s/SchemaReader.hpp @@ -185,6 +185,16 @@ class SchemaReader { */ static int32_t get_first_column_in_span(std::span schema); + /** + * @return the timestamp found in the row pointed to by m_cur_message + */ + epochtime_t get_next_timestamp() const { return m_get_timestamp(); } + + /** + * @return true if all records in this table have been iterated over, false otherwise + */ + bool done() const { return m_cur_message >= m_num_messages; } + private: /** * Merges the current local schema tree with the section of the global schema tree corresponding diff --git a/components/core/src/clp_s/archive_constants.hpp b/components/core/src/clp_s/archive_constants.hpp index d5a89d0bf..30e2b78d5 100644 --- a/components/core/src/clp_s/archive_constants.hpp +++ b/components/core/src/clp_s/archive_constants.hpp @@ -15,5 +15,14 @@ constexpr char cArchiveArrayDictFile[] = "/array.dict"; constexpr char cArchiveLogDictFile[] = "/log.dict"; constexpr char cArchiveTimestampDictFile[] = "/timestamp.dict"; constexpr char cArchiveVarDictFile[] = "/var.dict"; + +namespace results_cache::decompression { +constexpr char cPath[]{"path"}; +constexpr char cOrigFileId[]{"orig_file_id"}; +constexpr char cBeginMsgIx[]{"begin_msg_ix"}; +constexpr char cEndMsgIx[]{"end_msg_ix"}; +constexpr char cIsLastIrChunk[]{"is_last_ir_chunk"}; +} // namespace results_cache::decompression + } // namespace clp_s::constants #endif // CLP_S_ARCHIVE_CONSTANTS_HPP diff --git a/components/core/src/clp_s/clp-s.cpp b/components/core/src/clp_s/clp-s.cpp index b1dda7508..0e0401ad1 100644 --- a/components/core/src/clp_s/clp-s.cpp +++ b/components/core/src/clp_s/clp-s.cpp @@ -245,6 +245,7 @@ int main(int argc, char const* argv[]) { } clp_s::TimestampPattern::init(); + mongocxx::instance const mongocxx_instance{}; CommandLineArguments command_line_arguments("clp-s"); auto parsing_result = command_line_arguments.parse_arguments(argc, argv); @@ -269,12 +270,20 @@ int main(int argc, char const* argv[]) { return 1; } - clp_s::JsonConstructorOption option; + clp_s::JsonConstructorOption option{}; option.output_dir = command_line_arguments.get_output_dir(); + option.ordered = command_line_arguments.get_ordered_decompression(); + option.archives_dir = archives_dir; + option.ordered_chunk_size = command_line_arguments.get_ordered_chunk_size(); + if (false == command_line_arguments.get_mongodb_uri().empty()) { + option.metadata_db + = {command_line_arguments.get_mongodb_uri(), + command_line_arguments.get_mongodb_collection()}; + } try { auto const& archive_id = command_line_arguments.get_archive_id(); if (false == archive_id.empty()) { - option.archives_dir = std::filesystem::path{archives_dir} / archive_id; + option.archive_id = archive_id; decompress_archive(option); } else { for (auto const& entry : std::filesystem::directory_iterator(archives_dir)) { @@ -283,7 +292,7 @@ int main(int argc, char const* argv[]) { continue; } - option.archives_dir = entry.path(); + option.archive_id = entry.path().filename(); decompress_archive(option); } } @@ -292,8 +301,6 @@ int main(int argc, char const* argv[]) { return 1; } } else { - mongocxx::instance const mongocxx_instance{}; - auto const& query = command_line_arguments.get_query(); auto query_stream = std::istringstream(query); auto expr = kql::parse_kql_expression(query_stream); @@ -330,10 +337,7 @@ int main(int argc, char const* argv[]) { auto const& archive_id = command_line_arguments.get_archive_id(); auto archive_reader = std::make_shared(); if (false == archive_id.empty()) { - std::filesystem::path const archives_dir_path{archives_dir}; - std::string const archive_path{archives_dir_path / archive_id}; - - archive_reader->open(archive_path); + archive_reader->open(archives_dir, archive_id); if (false == search_archive(command_line_arguments, archive_reader, expr, reducer_socket_fd)) { @@ -347,7 +351,8 @@ int main(int argc, char const* argv[]) { continue; } - archive_reader->open(entry.path()); + auto const archive_id = entry.path().filename().string(); + archive_reader->open(archives_dir, archive_id); if (false == search_archive( command_line_arguments, diff --git a/components/core/src/clp_s/search/Output.cpp b/components/core/src/clp_s/search/Output.cpp index 524c055fd..b6a3b8fe0 100644 --- a/components/core/src/clp_s/search/Output.cpp +++ b/components/core/src/clp_s/search/Output.cpp @@ -65,6 +65,7 @@ bool Output::filter() { populate_string_queries(top_level_expr); std::string message; + auto const archive_id = m_archive_reader->get_archive_id(); for (int32_t schema_id : matched_schemas) { m_expr_clp_query.clear(); m_expr_var_match_map.clear(); @@ -85,15 +86,15 @@ bool Output::filter() { auto& reader = m_archive_reader->read_table( schema_id, - m_output_handler->should_output_timestamp(), + m_output_handler->should_output_metadata(), m_should_marshal_records ); reader.initialize_filter(this); - if (m_output_handler->should_output_timestamp()) { + if (m_output_handler->should_output_metadata()) { epochtime_t timestamp; while (reader.get_next_message_with_timestamp(message, timestamp, this)) { - m_output_handler->write(message, timestamp); + m_output_handler->write(message, timestamp, archive_id); } } else { while (reader.get_next_message(message, this)) { diff --git a/components/core/src/clp_s/search/OutputHandler.cpp b/components/core/src/clp_s/search/OutputHandler.cpp index 7007e8f3b..8d6a1eaa5 100644 --- a/components/core/src/clp_s/search/OutputHandler.cpp +++ b/components/core/src/clp_s/search/OutputHandler.cpp @@ -1,6 +1,8 @@ #include "OutputHandler.hpp" #include +#include +#include #include @@ -9,9 +11,12 @@ #include "../../reducer/network_utils.hpp" #include "../../reducer/Record.hpp" +using std::string; +using std::string_view; + namespace clp_s::search { NetworkOutputHandler::NetworkOutputHandler( - std::string const& host, + string const& host, int port, bool should_output_timestamp ) @@ -23,18 +28,14 @@ NetworkOutputHandler::NetworkOutputHandler( } } -void NetworkOutputHandler::write(std::string const& message, epochtime_t timestamp) { - msgpack::type::tuple src("", timestamp, message); - msgpack::sbuffer m; - msgpack::pack(m, src); - - if (-1 == send(m_socket_fd, m.data(), m.size(), 0)) { - throw OperationFailed(ErrorCode::ErrorCodeFailureNetwork, __FILE__, __LINE__); - } -} - -void NetworkOutputHandler::write(std::string const& message) { - msgpack::type::tuple src("", 0, message); +void NetworkOutputHandler::write( + string_view message, + epochtime_t timestamp, + string_view archive_id +) { + static constexpr string_view cOrigFilePathPlaceholder{""}; + msgpack::type::tuple const + src(timestamp, message, cOrigFilePathPlaceholder, archive_id); msgpack::sbuffer m; msgpack::pack(m, src); @@ -44,8 +45,8 @@ void NetworkOutputHandler::write(std::string const& message) { } ResultsCacheOutputHandler::ResultsCacheOutputHandler( - std::string const& uri, - std::string const& collection, + string const& uri, + string const& collection, uint64_t batch_size, uint64_t max_num_results, bool should_output_timestamp @@ -73,7 +74,8 @@ ErrorCode ResultsCacheOutputHandler::flush() { m_results.emplace_back(std::move(bsoncxx::builder::basic::make_document( bsoncxx::builder::basic::kvp("original_path", std::move(result.original_path)), bsoncxx::builder::basic::kvp("message", std::move(result.message)), - bsoncxx::builder::basic::kvp("timestamp", result.timestamp) + bsoncxx::builder::basic::kvp("timestamp", result.timestamp), + bsoncxx::builder::basic::kvp("archive_id", std::move(result.archive_id)) ))); count++; @@ -98,12 +100,20 @@ ErrorCode ResultsCacheOutputHandler::flush() { return ErrorCode::ErrorCodeSuccess; } -void ResultsCacheOutputHandler::write(std::string const& message, epochtime_t timestamp) { +void ResultsCacheOutputHandler::write( + string_view message, + epochtime_t timestamp, + string_view archive_id +) { if (m_latest_results.size() < m_max_num_results) { - m_latest_results.emplace(std::make_unique("", message, timestamp)); + m_latest_results.emplace( + std::make_unique(string_view{}, message, timestamp, archive_id) + ); } else if (m_latest_results.top()->timestamp < timestamp) { m_latest_results.pop(); - m_latest_results.emplace(std::make_unique("", message, timestamp)); + m_latest_results.emplace( + std::make_unique(string_view{}, message, timestamp, archive_id) + ); } } @@ -114,7 +124,7 @@ CountOutputHandler::CountOutputHandler(int reducer_socket_fd) m_pipeline.add_pipeline_stage(std::make_shared()); } -void CountOutputHandler::write(std::string const& message) { +void CountOutputHandler::write(string_view message) { m_pipeline.push_record(reducer::EmptyRecord{}); } diff --git a/components/core/src/clp_s/search/OutputHandler.hpp b/components/core/src/clp_s/search/OutputHandler.hpp index 624c006be..ca12e32d3 100644 --- a/components/core/src/clp_s/search/OutputHandler.hpp +++ b/components/core/src/clp_s/search/OutputHandler.hpp @@ -5,8 +5,10 @@ #include #include +#include #include #include +#include #include #include @@ -28,8 +30,8 @@ namespace clp_s::search { class OutputHandler { public: // Constructors - explicit OutputHandler(bool should_output_timestamp, bool should_marshal_records) - : m_should_output_timestamp(should_output_timestamp), + explicit OutputHandler(bool should_output_metadata, bool should_marshal_records) + : m_should_output_metadata(should_output_metadata), m_should_marshal_records(should_marshal_records) {}; // Destructor @@ -40,14 +42,16 @@ class OutputHandler { * Writes a log event to the output handler. * @param message The message in the log event. * @param timestamp The timestamp of the log event. + * @param archive_id The archive containing the log event. */ - virtual void write(std::string const& message, epochtime_t timestamp) = 0; + virtual void write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) + = 0; /** * Writes a message to the output handler. * @param message The message to write. */ - virtual void write(std::string const& message) = 0; + virtual void write(std::string_view message) = 0; /** * Flushes the output handler after each table that gets searched. @@ -61,12 +65,12 @@ class OutputHandler { */ virtual ErrorCode finish() { return ErrorCode::ErrorCodeSuccess; } - [[nodiscard]] bool should_output_timestamp() const { return m_should_output_timestamp; } + [[nodiscard]] bool should_output_metadata() const { return m_should_output_metadata; } [[nodiscard]] bool should_marshal_records() const { return m_should_marshal_records; } private: - bool m_should_output_timestamp; + bool m_should_output_metadata; bool m_should_marshal_records; }; @@ -76,15 +80,16 @@ class OutputHandler { class StandardOutputHandler : public OutputHandler { public: // Constructors - explicit StandardOutputHandler(bool should_output_timestamp = false) - : OutputHandler(should_output_timestamp, true) {} + explicit StandardOutputHandler(bool should_output_metadata = false) + : OutputHandler(should_output_metadata, true) {} // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override { - printf("%" EPOCHTIME_T_PRINTF_FMT " %s", timestamp, message.c_str()); + void + write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) override { + std::cout << archive_id << ": " << timestamp << " " << message; } - void write(std::string const& message) override { printf("%s", message.c_str()); } + void write(std::string_view message) override { std::cout << message; } }; /** @@ -104,7 +109,7 @@ class NetworkOutputHandler : public OutputHandler { explicit NetworkOutputHandler( std::string const& host, int port, - bool should_output_timestamp = false + bool should_output_metadata = false ); // Destructor @@ -115,9 +120,10 @@ class NetworkOutputHandler : public OutputHandler { } // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override; + void + write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) override; - void write(std::string const& message) override; + void write(std::string_view message) override { write(message, 0, {}); } private: std::string m_host; @@ -133,14 +139,21 @@ class ResultsCacheOutputHandler : public OutputHandler { // Types struct QueryResult { // Constructors - QueryResult(std::string original_path, std::string message, epochtime_t timestamp) - : original_path(std::move(original_path)), - message(std::move(message)), - timestamp(timestamp) {} + QueryResult( + std::string_view original_path, + std::string_view message, + epochtime_t timestamp, + std::string_view archive_id + ) + : original_path(original_path), + message(message), + timestamp(timestamp), + archive_id(archive_id) {} std::string original_path; std::string message; epochtime_t timestamp; + std::string archive_id; }; struct QueryResultGreaterTimestampComparator { @@ -165,7 +178,7 @@ class ResultsCacheOutputHandler : public OutputHandler { std::string const& collection, uint64_t batch_size, uint64_t max_num_results, - bool should_output_timestamp = true + bool should_output_metadata = true ); // Methods inherited from OutputHandler @@ -176,9 +189,10 @@ class ResultsCacheOutputHandler : public OutputHandler { */ ErrorCode flush() override; - void write(std::string const& message, epochtime_t timestamp) override; + void + write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) override; - void write(std::string const& message) override { write(message, 0); } + void write(std::string_view message) override { write(message, 0, {}); } private: mongocxx::client m_client; @@ -202,9 +216,10 @@ class CountOutputHandler : public OutputHandler { CountOutputHandler(int reducer_socket_fd); // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override {} + void + write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) override {} - void write(std::string const& message) override; + void write(std::string_view message) override; /** * Flushes the count. @@ -231,12 +246,13 @@ class CountByTimeOutputHandler : public OutputHandler { m_count_by_time_bucket_size{count_by_time_bucket_size} {} // Methods inherited from OutputHandler - void write(std::string const& message, epochtime_t timestamp) override { + void + write(std::string_view message, epochtime_t timestamp, std::string_view archive_id) override { int64_t bucket = (timestamp / m_count_by_time_bucket_size) * m_count_by_time_bucket_size; m_bucket_counts[bucket] += 1; } - void write(std::string const& message) override {} + void write(std::string_view message) override {} /** * Flushes the counts. diff --git a/components/core/src/glt/ir/LogEventDeserializer.cpp b/components/core/src/glt/ir/LogEventDeserializer.cpp index 00cd880e9..c22f22bb7 100644 --- a/components/core/src/glt/ir/LogEventDeserializer.cpp +++ b/components/core/src/glt/ir/LogEventDeserializer.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include "../ffi/ir_stream/decoding_methods.hpp" @@ -11,7 +12,7 @@ namespace glt::ir { template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { ffi::ir_stream::encoded_tag_t metadata_type{0}; std::vector metadata; auto ir_error_code = ffi::ir_stream::deserialize_preamble(reader, metadata_type, metadata); @@ -67,7 +68,7 @@ auto LogEventDeserializer::create(ReaderInterface& reader template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result> { +) -> OUTCOME_V2_NAMESPACE::std_result> { epoch_time_ms_t timestamp_or_timestamp_delta{}; std::string logtype; std::vector dict_vars; @@ -106,11 +107,11 @@ auto LogEventDeserializer::deserialize_log_event( // Explicitly declare template specializations so that we can define the template methods in this // file template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::create(ReaderInterface& reader -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; template auto LogEventDeserializer::deserialize_log_event( -) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; +) -> OUTCOME_V2_NAMESPACE::std_result>; } // namespace glt::ir diff --git a/components/core/src/glt/ir/LogEventDeserializer.hpp b/components/core/src/glt/ir/LogEventDeserializer.hpp index 9667f889a..4fbfb3c53 100644 --- a/components/core/src/glt/ir/LogEventDeserializer.hpp +++ b/components/core/src/glt/ir/LogEventDeserializer.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include "../ReaderInterface.hpp" #include "../TimestampPattern.hpp" @@ -34,7 +34,7 @@ class LogEventDeserializer { * or uses an unsupported version */ static auto create(ReaderInterface& reader - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; // Delete copy constructor and assignment LogEventDeserializer(LogEventDeserializer const&) = delete; @@ -59,7 +59,7 @@ class LogEventDeserializer { * - std::errc::result_out_of_range if the IR stream is corrupted */ [[nodiscard]] auto deserialize_log_event( - ) -> BOOST_OUTCOME_V2_NAMESPACE::std_result>; + ) -> OUTCOME_V2_NAMESPACE::std_result>; private: // Constructors diff --git a/components/core/src/reducer/CommandLineArguments.cpp b/components/core/src/reducer/CommandLineArguments.cpp index 2429fd9dc..fe9cf4a72 100644 --- a/components/core/src/reducer/CommandLineArguments.cpp +++ b/components/core/src/reducer/CommandLineArguments.cpp @@ -30,12 +30,12 @@ CommandLineArguments::parse_arguments(int argc, char const* argv[]) { "scheduler-host", po::value(&m_scheduler_host) ->default_value(m_scheduler_host), - "Host the search scheduler is running on" + "Host the query scheduler is running on" )( "scheduler-port", po::value(&m_scheduler_port) ->default_value(m_scheduler_port), - "Port the search scheduler is listening on" + "Port the query scheduler is listening on" )( "mongodb-uri", po::value(&m_mongodb_uri) diff --git a/components/core/src/reducer/ServerContext.cpp b/components/core/src/reducer/ServerContext.cpp index 8faa59c55..75b667da4 100644 --- a/components/core/src/reducer/ServerContext.cpp +++ b/components/core/src/reducer/ServerContext.cpp @@ -89,7 +89,7 @@ bool ServerContext::register_with_scheduler( try { boost::asio::connect(m_scheduler_socket, endpoints); } catch (boost::system::system_error& error) { - SPDLOG_ERROR("Failed to connect to search scheduler - {}", error.what()); + SPDLOG_ERROR("Failed to connect to query scheduler - {}", error.what()); return false; } @@ -120,12 +120,12 @@ bool ServerContext::register_with_scheduler( return true; } -bool ServerContext::ack_search_scheduler() { +bool ServerContext::ack_query_scheduler() { boost::system::error_code e; char const scheduler_response = 'y'; boost::asio::write(m_scheduler_socket, boost::asio::buffer(&scheduler_response, 1), e); if (e) { - SPDLOG_ERROR("Failed to notify search scheduler - {}", e.message()); + SPDLOG_ERROR("Failed to notify query scheduler - {}", e.message()); return false; } return true; @@ -253,7 +253,7 @@ bool ServerContext::try_finalize_results() { return false; } - // Notify the search scheduler that the results have been pushed - return ack_search_scheduler(); + // Notify the query scheduler that the results have been pushed + return ack_query_scheduler(); } } // namespace reducer diff --git a/components/core/src/reducer/ServerContext.hpp b/components/core/src/reducer/ServerContext.hpp index dc6fb1c4d..fbc2065ef 100644 --- a/components/core/src/reducer/ServerContext.hpp +++ b/components/core/src/reducer/ServerContext.hpp @@ -79,10 +79,10 @@ class ServerContext { bool register_with_scheduler(boost::asio::ip::tcp::resolver::results_type const& endpoints); /** - * Synchronously sends a generic acknowledgement to the search scheduler. + * Synchronously sends a generic acknowledgement to the query scheduler. * @return Whether the acknowledgement was sent successfully. */ - bool ack_search_scheduler(); + bool ack_query_scheduler(); /** * Increments the number of active receiver tasks which may receive some results. @@ -124,7 +124,7 @@ class ServerContext { /** * If all results have been received then this function tries to publish the pipeline's results - * to the results cache and notify the search scheduler. + * to the results cache and notify the query scheduler. * @return true if not all results have been received yet, or the results were published * successfully. * @return false otherwise. diff --git a/components/core/src/reducer/reducer_server.cpp b/components/core/src/reducer/reducer_server.cpp index 4c7bca348..ab35b7396 100644 --- a/components/core/src/reducer/reducer_server.cpp +++ b/components/core/src/reducer/reducer_server.cpp @@ -212,7 +212,7 @@ void SchedulerUpdateListenerTask::operator()( } // Synchronously notify the scheduler that the reducer is ready - if (false == m_server_ctx->ack_search_scheduler()) { + if (false == m_server_ctx->ack_query_scheduler()) { m_server_ctx->set_status(ServerStatus::RecoverableFailure); m_server_ctx->stop_event_loop(); return; diff --git a/components/core/submodules/boost-outcome b/components/core/submodules/boost-outcome deleted file mode 160000 index 39500a331..000000000 --- a/components/core/submodules/boost-outcome +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 39500a33117c23596673c1925479c7ff01b602f6 diff --git a/components/core/submodules/outcome b/components/core/submodules/outcome new file mode 160000 index 000000000..571f9c930 --- /dev/null +++ b/components/core/submodules/outcome @@ -0,0 +1 @@ +Subproject commit 571f9c930e672950e99d5d30f743603aaaf8014c diff --git a/components/core/tests/test-MemoryMappedFile.cpp b/components/core/tests/test-MemoryMappedFile.cpp new file mode 100644 index 000000000..20d433f32 --- /dev/null +++ b/components/core/tests/test-MemoryMappedFile.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/FileReader.hpp" +#include "../src/clp/ReaderInterface.hpp" +#include "../src/clp/ReadOnlyMemoryMappedFile.hpp" + +namespace { +/** + * Reads all content from a reader. + * @param reader + * @return The content. + */ +[[nodiscard]] auto read_content(clp::ReaderInterface& reader) -> std::vector; + +[[nodiscard]] auto get_test_dir() -> std::filesystem::path; + +auto read_content(clp::ReaderInterface& reader) -> std::vector { + constexpr size_t cBufferSize{4096}; + std::array read_buf{}; + std::vector buf; + for (bool has_more_content{true}; has_more_content;) { + size_t num_bytes_read{}; + has_more_content = reader.read(read_buf.data(), read_buf.size(), num_bytes_read); + buf.insert(buf.cend(), read_buf.cbegin(), read_buf.cbegin() + num_bytes_read); + } + return buf; +} + +auto get_test_dir() -> std::filesystem::path { + std::filesystem::path const current_file_path{__FILE__}; + return current_file_path.parent_path(); +} +} // namespace + +TEST_CASE("memory_mapped_file_view_basic", "[ReadOnlyMemoryMappedFile]") { + auto const test_input_path{ + get_test_dir() / std::filesystem::path{"test_network_reader_src"} / "random.log" + }; + clp::FileReader file_reader; + file_reader.open(test_input_path.string()); + auto const expected{read_content(file_reader)}; + file_reader.close(); + + clp::ReadOnlyMemoryMappedFile const mmap_file{test_input_path.string()}; + auto const view{mmap_file.get_view()}; + REQUIRE((view.size() == expected.size())); + REQUIRE(std::equal(view.begin(), view.end(), expected.cbegin())); +} + +TEST_CASE("memory_mapped_file_view_empty", "[ReadOnlyMemoryMappedFile]") { + auto const test_input_path{ + get_test_dir() / std::filesystem::path{"test_schema_files"} / "empty_schema.txt" + }; + + clp::ReadOnlyMemoryMappedFile const mmap_file{test_input_path.string()}; + auto const view{mmap_file.get_view()}; + REQUIRE(view.empty()); +} diff --git a/components/core/tests/test-NetworkReader.cpp b/components/core/tests/test-NetworkReader.cpp index 958e31d8b..d06876f5f 100644 --- a/components/core/tests/test-NetworkReader.cpp +++ b/components/core/tests/test-NetworkReader.cpp @@ -12,6 +12,7 @@ #include #include "../src/clp/CurlDownloadHandler.hpp" +#include "../src/clp/CurlGlobalInstance.hpp" #include "../src/clp/ErrorCode.hpp" #include "../src/clp/FileReader.hpp" #include "../src/clp/NetworkReader.hpp" @@ -71,7 +72,7 @@ TEST_CASE("network_reader_basic", "[NetworkReader]") { auto const expected{get_content(ref_reader)}; ref_reader.close(); - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url()}; auto const actual{get_content(reader)}; auto const ret_code{reader.get_curl_ret_code()}; @@ -79,7 +80,6 @@ TEST_CASE("network_reader_basic", "[NetworkReader]") { // NOLINTNEXTLINE(bugprone-unchecked-optional-access) REQUIRE((CURLE_OK == ret_code.value())); REQUIRE((actual == expected)); - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_with_offset_and_seek", "[NetworkReader]") { @@ -91,10 +91,9 @@ TEST_CASE("network_reader_with_offset_and_seek", "[NetworkReader]") { auto const ref_end_pos{ref_reader.get_pos()}; ref_reader.close(); - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); - // Read from an offset onwards by starting the download from that offset. { + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url(), cOffset}; auto const actual{get_content(reader)}; auto const ret_code{reader.get_curl_ret_code()}; @@ -107,6 +106,7 @@ TEST_CASE("network_reader_with_offset_and_seek", "[NetworkReader]") { // Read from an offset onwards by seeking to that offset. { + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader(get_test_input_remote_url()); reader.seek_from_begin(cOffset); auto const actual{get_content(reader)}; @@ -117,19 +117,16 @@ TEST_CASE("network_reader_with_offset_and_seek", "[NetworkReader]") { REQUIRE((reader.get_pos() == ref_end_pos)); REQUIRE((actual == expected)); } - - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_destruct", "[NetworkReader]") { - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); - // We sleep to fill out all the buffers, and then we delete the reader. The destructor will try // to abort the underlying download and then destroy the instance. So should ensure destructor // is successfully executed without deadlock or exceptions. bool no_exception{true}; try { // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + clp::CurlGlobalInstance const curl_global_instance; auto reader{std::make_unique( get_test_input_remote_url(), 0, @@ -147,15 +144,12 @@ TEST_CASE("network_reader_destruct", "[NetworkReader]") { no_exception = false; } REQUIRE(no_exception); - - clp::NetworkReader::deinit(); } TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") { - REQUIRE((clp::ErrorCode_Success == clp::NetworkReader::init())); - // Try to read from an out-of-bound offset. constexpr size_t cIllegalOffset{UINT32_MAX}; + clp::CurlGlobalInstance const curl_global_instance; clp::NetworkReader reader{get_test_input_remote_url(), cIllegalOffset}; while (true) { auto const ret_code{reader.get_curl_ret_code()}; @@ -166,6 +160,4 @@ TEST_CASE("network_reader_illegal_offset", "[NetworkReader]") { break; } } - - clp::NetworkReader::deinit(); } diff --git a/components/core/tests/test-StreamingCompression.cpp b/components/core/tests/test-StreamingCompression.cpp index cad66d028..b43316b3f 100644 --- a/components/core/tests/test-StreamingCompression.cpp +++ b/components/core/tests/test-StreamingCompression.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include diff --git a/components/core/tests/test-hash_utils.cpp b/components/core/tests/test-hash_utils.cpp new file mode 100644 index 000000000..9e5aacd47 --- /dev/null +++ b/components/core/tests/test-hash_utils.cpp @@ -0,0 +1,51 @@ +#include +#include + +#include + +#include "../src/clp/ErrorCode.hpp" +#include "../src/clp/hash_utils.hpp" +#include "../src/clp/type_utils.hpp" + +using clp::convert_to_hex_string; +using clp::ErrorCode_Success; +using clp::get_hmac_sha256_hash; +using clp::get_sha256_hash; +using clp::size_checked_pointer_cast; +using std::string_view; +using std::vector; + +TEST_CASE("test_sha256", "[hash_utils]") { + constexpr string_view cInputString{"ThisIsARandomTestInput"}; + constexpr string_view cReferenceSha256{ + "c3a1d9f04ada1198c4c63bf51d9933fc2cc216429275cadabdcb2178775add0c" + }; + vector hash; + + REQUIRE(ErrorCode_Success + == get_sha256_hash( + {size_checked_pointer_cast(cInputString.data()), + cInputString.size()}, + hash + )); + REQUIRE(convert_to_hex_string(hash) == cReferenceSha256); +} + +TEST_CASE("test_hmac", "[hash_utils]") { + constexpr string_view cInputString{"ThisIsARandomTestInput"}; + constexpr string_view cInputKey{"ThisIsATestKey"}; + constexpr string_view cReferenceHmacSha256{ + "38373057694c1038a6895212bea46849eb7a59b73a2ec175883ae095fb91ffda" + }; + vector hmac_hash; + + REQUIRE(ErrorCode_Success + == get_hmac_sha256_hash( + {size_checked_pointer_cast(cInputString.data()), + cInputString.size()}, + {size_checked_pointer_cast(cInputKey.data()), + cInputKey.size()}, + hmac_hash + )); + REQUIRE(convert_to_hex_string(hmac_hash) == cReferenceHmacSha256); +} diff --git a/components/core/tests/test-ir_encoding_methods.cpp b/components/core/tests/test-ir_encoding_methods.cpp index e56ea4e86..e9b161b8a 100644 --- a/components/core/tests/test-ir_encoding_methods.cpp +++ b/components/core/tests/test-ir_encoding_methods.cpp @@ -1,14 +1,22 @@ +#include +#include #include +#include +#include #include +#include #include #include +#include #include "../src/clp/BufferReader.hpp" +#include "../src/clp/ErrorCode.hpp" #include "../src/clp/ffi/encoding_methods.hpp" #include "../src/clp/ffi/ir_stream/decoding_methods.hpp" #include "../src/clp/ffi/ir_stream/encoding_methods.hpp" #include "../src/clp/ffi/ir_stream/protocol_constants.hpp" +#include "../src/clp/ffi/ir_stream/Serializer.hpp" #include "../src/clp/ir/LogEventDeserializer.hpp" #include "../src/clp/ir/types.hpp" #include "../src/clp/time_types.hpp" @@ -30,6 +38,7 @@ using clp::ffi::ir_stream::encoded_tag_t; using clp::ffi::ir_stream::get_encoding_type; using clp::ffi::ir_stream::IRErrorCode; using clp::ffi::ir_stream::serialize_utc_offset_change; +using clp::ffi::ir_stream::Serializer; using clp::ffi::ir_stream::validate_protocol_version; using clp::ffi::wildcard_query_matches_any_encoded_var; using clp::ir::eight_byte_encoded_variable_t; @@ -115,6 +124,32 @@ template */ [[nodiscard]] auto get_current_ts() -> epoch_time_ms_t; +/** + * Flushes and clears serialized data from the serializer's underlying IR buffer into the given byte + * buffer. + * @tparam encoded_variable_t + * @param serializer + * @param byte_buf + */ +template +auto flush_and_clear_serializer_buffer( + Serializer& serializer, + std::vector& byte_buf +) -> void; + +/** + * Unpacks and serializes the given msgpack bytes using kv serializer. + * @tparam encoded_variable_t + * @param msgpack_bytes + * @param serializer + * @return Whether serialization succeeded. + */ +template +[[nodiscard]] auto unpack_and_serialize_msgpack_bytes( + vector const& msgpack_bytes, + Serializer& serializer +) -> bool; + template [[nodiscard]] auto serialize_log_events( vector const& log_events, @@ -214,6 +249,32 @@ auto create_test_log_events() -> vector { auto get_current_ts() -> epoch_time_ms_t { return duration_cast(system_clock::now().time_since_epoch()).count(); } + +template +auto flush_and_clear_serializer_buffer( + Serializer& serializer, + vector& byte_buf +) -> void { + auto const view{serializer.get_ir_buf_view()}; + byte_buf.insert(byte_buf.cend(), view.begin(), view.end()); + serializer.clear_ir_buf(); +} + +template +auto unpack_and_serialize_msgpack_bytes( + vector const& msgpack_bytes, + Serializer& serializer +) -> bool { + auto const msgpack_obj_handle{msgpack::unpack( + clp::size_checked_pointer_cast(msgpack_bytes.data()), + msgpack_bytes.size() + )}; + auto const msgpack_obj{msgpack_obj_handle.get()}; + if (msgpack::type::MAP != msgpack_obj.type) { + return false; + } + return serializer.serialize_msgpack_map(msgpack_obj.via.map); +} } // namespace /** @@ -851,10 +912,176 @@ TEMPLATE_TEST_CASE( REQUIRE(log_event.get_utc_offset() == ref_log_event.get_utc_offset()); // We only compare the logtype since decoding messages from logtype + variables is not yet // supported by our public interfaces - REQUIRE(log_event.get_logtype() == encoded_logtypes.at(log_event_idx)); + REQUIRE(log_event.get_message().get_logtype() == encoded_logtypes.at(log_event_idx)); ++log_event_idx; } auto result = log_event_deserializer.deserialize_log_event(); REQUIRE(result.has_error()); REQUIRE(std::errc::no_message_available == result.error()); } + +TEMPLATE_TEST_CASE( + "ffi_ir_stream_Serializer_creation", + "[clp][ffi][ir_stream][Serializer]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + // This is a unit test for the kv-pair IR serializer. Currently, we haven't yet implemented a + // deserializer, so we can only test whether the preamble packet is serialized correctly. + vector ir_buf; + + auto result{Serializer::create()}; + REQUIRE((false == result.has_error())); + + auto& serializer{result.value()}; + flush_and_clear_serializer_buffer(serializer, ir_buf); + REQUIRE(serializer.get_ir_buf_view().empty()); + + constexpr UtcOffset cBeijingUtcOffset{8 * 60 * 60 * 1000}; + serializer.change_utc_offset(cBeijingUtcOffset); + flush_and_clear_serializer_buffer(serializer, ir_buf); + REQUIRE(serializer.get_ir_buf_view().empty()); + + ir_buf.push_back(clp::ffi::ir_stream::cProtocol::Eof); + + BufferReader buffer_reader{size_checked_pointer_cast(ir_buf.data()), ir_buf.size()}; + + bool is_four_byte_encoding{}; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == get_encoding_type(buffer_reader, is_four_byte_encoding)) + ); + if constexpr (std::is_same_v) { + REQUIRE(is_four_byte_encoding); + } else { + REQUIRE((false == is_four_byte_encoding)); + } + + encoded_tag_t metadata_type{}; + vector metadata_bytes; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == deserialize_preamble(buffer_reader, metadata_type, metadata_bytes)) + ); + REQUIRE((clp::ffi::ir_stream::cProtocol::Metadata::EncodingJson == metadata_type)); + string_view const metadata_view{ + size_checked_pointer_cast(metadata_bytes.data()), + metadata_bytes.size() + }; + nlohmann::json const metadata = nlohmann::json::parse(metadata_view); + + nlohmann::json expected_metadata; + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VersionKey, + clp::ffi::ir_stream::cProtocol::Metadata::BetaVersionValue + ); + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VariablesSchemaIdKey, + clp::ffi::cVariablesSchemaVersion + ); + expected_metadata.emplace( + clp::ffi::ir_stream::cProtocol::Metadata::VariableEncodingMethodsIdKey, + clp::ffi::cVariableEncodingMethodsVersion + ); + REQUIRE((expected_metadata == metadata)); + + encoded_tag_t encoded_tag{}; + REQUIRE((IRErrorCode::IRErrorCode_Success == deserialize_tag(buffer_reader, encoded_tag))); + REQUIRE((clp::ffi::ir_stream::cProtocol::Payload::UtcOffsetChange == encoded_tag)); + UtcOffset utc_offset_change{0}; + REQUIRE( + (IRErrorCode::IRErrorCode_Success + == deserialize_utc_offset_change(buffer_reader, utc_offset_change)) + ); + REQUIRE((cBeijingUtcOffset == utc_offset_change)); + + REQUIRE((IRErrorCode::IRErrorCode_Success == deserialize_tag(buffer_reader, encoded_tag))); + REQUIRE((clp::ffi::ir_stream::cProtocol::Eof == encoded_tag)); + + char eof{}; + size_t num_bytes_read{}; + REQUIRE( + (clp::ErrorCode_EndOfFile == buffer_reader.try_read(&eof, 1, num_bytes_read) + && 0 == num_bytes_read) + ); +} + +TEMPLATE_TEST_CASE( + "ffi_ir_stream_Serializer_serialize_msgpack", + "[clp][ffi][ir_stream][Serializer]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + // TODO: Test deserializing the serialized bytes once a KV-pair IR deserializer is implemented. + + vector ir_buf; + + auto result{Serializer::create()}; + REQUIRE((false == result.has_error())); + + auto& serializer{result.value()}; + flush_and_clear_serializer_buffer(serializer, ir_buf); + + auto const empty_obj = nlohmann::json::parse("{}"); + REQUIRE(unpack_and_serialize_msgpack_bytes(nlohmann::json::to_msgpack(empty_obj), serializer)); + + // Test encoding basic object + constexpr string_view cShortString{"short_string"}; + constexpr string_view cClpString{"uid=0, CPU usage: 99.99%, \"user_name\"=YScope"}; + auto const empty_array = nlohmann::json::parse("[]"); + nlohmann::json const basic_obj + = {{"int8_max", INT8_MAX}, + {"int8_min", INT8_MIN}, + {"int16_max", INT16_MAX}, + {"int16_min", INT16_MIN}, + {"int32_max", INT32_MAX}, + {"int32_min", INT32_MIN}, + {"int64_max", INT64_MAX}, + {"int64_min", INT64_MIN}, + {"float_zero", 0.0}, + {"float_pos", 1.01}, + {"float_neg", -1.01}, + {"true", true}, + {"false", false}, + {"string", cShortString}, + {"clp_string", cClpString}, + {"null", nullptr}, + {"empty_object", empty_obj}, + {"empty_array", empty_array}}; + + REQUIRE(unpack_and_serialize_msgpack_bytes(nlohmann::json::to_msgpack(basic_obj), serializer)); + + auto basic_array = empty_array; + basic_array.emplace_back(1); + basic_array.emplace_back(1.0); + basic_array.emplace_back(true); + basic_array.emplace_back(cShortString); + basic_array.emplace_back(cClpString); + basic_array.emplace_back(nullptr); + basic_array.emplace_back(empty_array); + for (auto const& element : basic_array) { + // Non-map objects should not be serializable + REQUIRE( + (false + == unpack_and_serialize_msgpack_bytes( + nlohmann::json::to_msgpack(element), + serializer + )) + ); + } + basic_array.emplace_back(empty_obj); + + // Recursively construct an object containing inner maps and inner arrays. + auto recursive_obj = basic_obj; + auto recursive_array = basic_array; + constexpr size_t cRecursiveDepth{6}; + for (size_t i{0}; i < cRecursiveDepth; ++i) { + recursive_array.emplace_back(recursive_obj); + recursive_obj.emplace("obj_" + std::to_string(i), recursive_obj); + recursive_obj.emplace("array_" + std::to_string(i), recursive_array); + REQUIRE(unpack_and_serialize_msgpack_bytes( + nlohmann::json::to_msgpack(recursive_obj), + serializer + )); + } +} diff --git a/components/core/tests/test-ir_serializer.cpp b/components/core/tests/test-ir_serializer.cpp new file mode 100644 index 000000000..488f91d36 --- /dev/null +++ b/components/core/tests/test-ir_serializer.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "../src/clp/ffi/ir_stream/decoding_methods.hpp" +#include "../src/clp/ir/constants.hpp" +#include "../src/clp/ir/LogEventDeserializer.hpp" +#include "../src/clp/ir/LogEventSerializer.hpp" +#include "../src/clp/ir/types.hpp" +#include "../src/clp/streaming_compression/zstd/Decompressor.hpp" + +using clp::ffi::ir_stream::IRErrorCode::IRErrorCode_Success; +using clp::ir::cIrFileExtension; +using clp::ir::eight_byte_encoded_variable_t; +using clp::ir::epoch_time_ms_t; +using clp::ir::four_byte_encoded_variable_t; +using clp::ir::LogEventDeserializer; +using clp::ir::LogEventSerializer; +using clp::streaming_compression::zstd::Decompressor; +using std::chrono::milliseconds; +using std::chrono::system_clock; +using std::is_same_v; +using std::string; +using std::vector; + +namespace { +struct TestLogEvent { + epoch_time_ms_t timestamp; + string msg; +}; +} // namespace + +TEMPLATE_TEST_CASE( + "Encode and serialize log events", + "[ir][serialize-log-event]", + four_byte_encoded_variable_t, + eight_byte_encoded_variable_t +) { + vector test_log_events; + + auto const ts_1 = duration_cast(system_clock::now().time_since_epoch()).count(); + vector const log_event_1_tokens + = {"Here is the first string with a small int ", + "4938", + " and a medium int ", + std::to_string(INT32_MAX), + " and a very large int ", + std::to_string(INT64_MAX), + " and a small float ", + "0.1", + "\n"}; + auto const log_event_1 + = std::accumulate(log_event_1_tokens.begin(), log_event_1_tokens.end(), string("")); + test_log_events.push_back({ts_1, log_event_1}); + + auto const ts_2 = duration_cast(system_clock::now().time_since_epoch()).count(); + vector const log_event_2_tokens + = {"Here is the second string with a medium float ", + "-25.519686", + " and a high precision float ", + "-25.5196868642755", + " and a weird float ", + "-00.00", + " and a string with numbers ", + "bin/python2.7.3", + " and another string with numbers ", + "abc123", + "\n"}; + auto const log_event_2 + = std::accumulate(log_event_2_tokens.begin(), log_event_2_tokens.end(), string("")); + test_log_events.push_back({ts_2, log_event_2}); + + string ir_test_file = "ir_serializer_test"; + ir_test_file += cIrFileExtension; + + LogEventSerializer serializer; + REQUIRE(serializer.open(ir_test_file)); + // Test serializing log events + for (auto const& test_log_event : test_log_events) { + REQUIRE(serializer.serialize_log_event(test_log_event.timestamp, test_log_event.msg)); + } + serializer.close(); + + Decompressor ir_reader; + ir_reader.open(ir_test_file); + + bool uses_four_byte_encoding{false}; + REQUIRE( + (IRErrorCode_Success + == clp::ffi::ir_stream::get_encoding_type(ir_reader, uses_four_byte_encoding)) + ); + REQUIRE((is_same_v == uses_four_byte_encoding)); + + auto result = LogEventDeserializer::create(ir_reader); + REQUIRE((false == result.has_error())); + auto& deserializer = result.value(); + + // Decode and deserialize all expected log events + for (auto const& test_log_event : test_log_events) { + auto deserialized_result = deserializer.deserialize_log_event(); + REQUIRE((false == deserialized_result.has_error())); + + auto& log_event = deserialized_result.value(); + auto const decoded_message = log_event.get_message().decode_and_unparse(); + REQUIRE(decoded_message.has_value()); + + REQUIRE((decoded_message.value() == test_log_event.msg)); + REQUIRE((log_event.get_timestamp() == test_log_event.timestamp)); + } + // Try decoding a nonexistent log event + auto deserialized_result = deserializer.deserialize_log_event(); + REQUIRE(deserialized_result.has_error()); + + std::filesystem::remove(ir_test_file); +} diff --git a/components/core/tests/test-query_methods.cpp b/components/core/tests/test-query_methods.cpp index d4555ac0b..b9b54a35e 100644 --- a/components/core/tests/test-query_methods.cpp +++ b/components/core/tests/test-query_methods.cpp @@ -165,6 +165,7 @@ TEMPLATE_TEST_CASE( message += " and a very large int " + var_strs[var_ix++]; message += " and a small double " + var_strs[var_ix++]; message += " and a medium double " + var_strs[var_ix++]; + message += " and a high precison double " + var_strs[var_ix++]; message += " and a weird double " + var_strs[var_ix++]; message += " and a string with numbers " + var_strs[var_ix++]; message += " and another string with numbers " + var_strs[var_ix++]; diff --git a/components/core/tests/test-regex_utils.cpp b/components/core/tests/test-regex_utils.cpp new file mode 100644 index 000000000..64af60318 --- /dev/null +++ b/components/core/tests/test-regex_utils.cpp @@ -0,0 +1,95 @@ +#include +#include +#include +#include + +using clp::regex_utils::ErrorCode; +using clp::regex_utils::regex_to_wildcard; +using clp::regex_utils::RegexToWildcardTranslatorConfig; + +TEST_CASE("regex_to_wildcard_simple_translations", "[regex_utils][re2wc][simple_translations]") { + REQUIRE(regex_to_wildcard("").value().empty()); + + REQUIRE((regex_to_wildcard("xyz").value() == "xyz")); + REQUIRE((regex_to_wildcard(". xyz .* zyx .").value() == "? xyz * zyx ?")); + REQUIRE((regex_to_wildcard(". xyz .+ zyx .*").value() == "? xyz ?* zyx *")); +} + +TEST_CASE("regex_to_wildcard_unescaped_metachar", "[regex_utils][re2wc][unescaped_metachar]") { + REQUIRE((regex_to_wildcard(".? xyz .* zyx .").error() == ErrorCode::UnsupportedQuestionMark)); + REQUIRE((regex_to_wildcard(". xyz .** zyx .").error() == ErrorCode::UntranslatableStar)); + REQUIRE((regex_to_wildcard(". xyz .*+ zyx .").error() == ErrorCode::UntranslatablePlus)); + REQUIRE((regex_to_wildcard(". xyz |.* zyx .").error() == ErrorCode::UnsupportedPipe)); + REQUIRE((regex_to_wildcard(". xyz ^.* zyx .").error() == ErrorCode::IllegalCaret)); + REQUIRE((regex_to_wildcard(". xyz $.* zyx .").error() == ErrorCode::IllegalDollarSign)); +} + +TEST_CASE("regex_to_wildcard_escaped_metachar", "[regex_utils][re2wc][escaped_metachar]") { + // Escape backslash is superfluous for the following set of characters + REQUIRE((regex_to_wildcard("<>-_/=!").value() == "<>-_/=!")); + REQUIRE((regex_to_wildcard("\\<\\>\\-\\_\\/\\=\\!").value() == "<>-_/=!")); + // Test the full escape sequences set + REQUIRE( + (regex_to_wildcard("\\*\\+\\?\\|\\^\\$\\.\\{\\}\\[\\]\\(\\)\\<\\>\\-\\_\\/\\=\\!\\\\") + .value() + == "\\*+\\?|^$.{}[]()<>-_/=!\\\\") + ); + // Test unsupported escape sequences + REQUIRE( + (regex_to_wildcard("abc\\Qdefghi\\Ejkl").error() + == clp::regex_utils::ErrorCode::IllegalEscapeSequence) + ); +} + +TEST_CASE("regex_to_wildcard_charset", "[regex_utils][re2wc][charset]") { + REQUIRE((regex_to_wildcard("x[y]z").value() == "xyz")); + REQUIRE((regex_to_wildcard("x[\\^]z").value() == "x^z")); + REQUIRE((regex_to_wildcard("x[\\]]z").value() == "x]z")); + REQUIRE((regex_to_wildcard("x[-]z").value() == "x-z")); + REQUIRE((regex_to_wildcard("x[\\-]z").value() == "x-z")); + REQUIRE((regex_to_wildcard("x[\\\\]z").value() == "x\\\\z")); + REQUIRE((regex_to_wildcard("[a][b][\\^][-][\\-][\\]][\\\\][c][d]").value() == "ab^--]\\\\cd")); + + REQUIRE((regex_to_wildcard("x[]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[a-z]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[^^]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("x[^0-9]y").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("[xX][yY]").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9]").error() == ErrorCode::UnsupportedCharsetPattern)); + + REQUIRE((regex_to_wildcard("[\\").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("[\\\\").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("[xX").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9").error() == ErrorCode::IncompleteCharsetStructure)); +} + +TEST_CASE("regex_to_wildcard_case_insensitive_config", "[regex_utils][re2wc][case_insensitive]") { + RegexToWildcardTranslatorConfig const config{/*case_insensitive_wildcard=*/true, false}; + REQUIRE((regex_to_wildcard("[xX][yY]", config).value() == "xy")); + REQUIRE((regex_to_wildcard("[Yy][Xx]", config).value() == "yx")); + REQUIRE((regex_to_wildcard("[aA][Bb][Cc]", config).value() == "abc")); + REQUIRE((regex_to_wildcard("[aA][Bb][\\^][-][\\]][Cc][dD]", config).value() == "ab^-]cd")); + + REQUIRE((regex_to_wildcard("[xX").error() == ErrorCode::IncompleteCharsetStructure)); + REQUIRE( + (regex_to_wildcard("[aA][Bb][^[-[\\[Cc[dD", config).error() + == ErrorCode::IncompleteCharsetStructure) + ); + REQUIRE((regex_to_wildcard("ch:[a-zA-Z0-9]").error() == ErrorCode::UnsupportedCharsetPattern)); + REQUIRE( + (regex_to_wildcard("[aA][Bb][^[-[\\[Cc[dD]", config).error() + == ErrorCode::UnsupportedCharsetPattern) + ); +} + +TEST_CASE("regex_to_wildcard_anchor_config", "[regex_utils][re2wc][anchor_config]") { + // Test anchors and prefix/suffix wildcards + RegexToWildcardTranslatorConfig const config{false, /*add_prefix_suffix_wildcards=*/true}; + REQUIRE(((regex_to_wildcard("^", config).value() == "*"))); + REQUIRE((regex_to_wildcard("$", config).value() == "*")); + REQUIRE((regex_to_wildcard("^xyz$", config).value() == "xyz")); + REQUIRE((regex_to_wildcard("xyz", config).value() == "*xyz*")); + REQUIRE((regex_to_wildcard("xyz$$", config).value() == "*xyz")); + + REQUIRE((regex_to_wildcard("xyz$zyx$", config).error() == ErrorCode::IllegalDollarSign)); +} diff --git a/components/core/tests/test-utf8_utils.cpp b/components/core/tests/test-utf8_utils.cpp new file mode 100644 index 000000000..77324eaf9 --- /dev/null +++ b/components/core/tests/test-utf8_utils.cpp @@ -0,0 +1,209 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "../src/clp/ffi/utils.hpp" +#include "../src/clp/utf8_utils.hpp" + +using clp::ffi::validate_and_escape_utf8_string; +using clp::is_utf8_encoded; + +namespace { +/** + * @param raw + * @return The input string after escaping any characters that are invalid in JSON strings. + */ +[[nodiscard]] auto get_expected_escaped_string(std::string_view raw) -> std::string; + +/** + * Generates a UTF-8 encoded byte sequence with the given code point and number of continuation + * bytes. The range of the code point is not validated, which means the generated byte sequence can + * be invalid (overlong or exceeding the valid range of UTF-8 code points). + * @param code_point + * @param num_continuation_bytes + * @return The encoded UTF-8 byte sequence. + */ +[[nodiscard]] auto +generate_utf8_byte_sequence(uint32_t code_point, size_t num_continuation_bytes) -> std::string; + +auto get_expected_escaped_string(std::string_view raw) -> std::string { + nlohmann::json const json_str = raw; // Don't use '{}' initializer + auto const dumped_str{json_str.dump()}; + // Strip the quotes that nlohmann::json adds + return {dumped_str.begin() + 1, dumped_str.end() - 1}; +} + +auto generate_utf8_byte_sequence(uint32_t code_point, size_t num_continuation_bytes) + -> std::string { + REQUIRE((1 <= num_continuation_bytes && num_continuation_bytes <= 3)); + std::vector encoded_bytes; + while (encoded_bytes.size() < num_continuation_bytes) { + auto const least_significant_byte{static_cast(code_point)}; + encoded_bytes.push_back(static_cast( + (least_significant_byte & ~clp::cUtf8ContinuationByteMask) + | clp::cUtf8ContinuationByteHeader + )); + code_point >>= clp::cUtf8NumContinuationByteCodePointBits; + } + + uint8_t lead_byte_code_point_mask{}; + uint8_t lead_byte_header{}; + if (1 == num_continuation_bytes) { + lead_byte_code_point_mask = static_cast(~clp::cTwoByteUtf8CharHeaderMask); + lead_byte_header = clp::cTwoByteUtf8CharHeader; + } else if (2 == num_continuation_bytes) { + lead_byte_code_point_mask = static_cast(~clp::cThreeByteUtf8CharHeaderMask); + lead_byte_header = clp::cThreeByteUtf8CharHeader; + } else { // 3 == num_continuation_bytes + lead_byte_code_point_mask = static_cast(~clp::cFourByteUtf8CharHeaderMask); + lead_byte_header = clp::cFourByteUtf8CharHeader; + } + encoded_bytes.push_back(static_cast( + (static_cast(code_point) & lead_byte_code_point_mask) | lead_byte_header + )); + + return {encoded_bytes.rbegin(), encoded_bytes.rend()}; +} +} // namespace + +TEST_CASE("escape_utf8_string_basic", "[utf8_utils]") { + std::string test_str; + std::optional actual; + + // Test empty string + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test string that has nothing to escape + test_str = "This string has nothing to escape :)"; + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test string with all single byte UTF-8 characters, including those we escape. + test_str.clear(); + for (uint8_t i{0}; i <= static_cast(INT8_MAX); ++i) { + test_str.push_back(static_cast(i)); + } + // Shuffle characters randomly + // NOLINTNEXTLINE(cert-msc32-c, cert-msc51-cpp) + std::shuffle(test_str.begin(), test_str.end(), std::default_random_engine{}); + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); + + // Test valid UTF-8 chars with continuation bytes + std::vector const valid_utf8{ + "\n", + "\xF0\xA0\x80\x8F", // https://en.wiktionary.org/wiki/%F0%A0%80%8F + "a", + "\xE4\xB8\xAD", // https://en.wiktionary.org/wiki/%E4%B8%AD + "\x1F", + "\xC2\xA2", // ¢ + "\\" + }; + test_str.clear(); + for (auto const& str : valid_utf8) { + test_str.append(str); + } + actual = validate_and_escape_utf8_string(test_str); + REQUIRE((actual.has_value() && actual.value() == get_expected_escaped_string(test_str))); +} + +TEST_CASE("escape_utf8_string_with_invalid_continuation", "[utf8_utils]") { + std::string test_str; + + auto const valid_utf8_byte_sequence = GENERATE( + generate_utf8_byte_sequence(0x80, 1), + generate_utf8_byte_sequence(0x800, 2), + generate_utf8_byte_sequence(0x1'0000, 3) + ); + + // Test incomplete continuation bytes + auto const begin_it{valid_utf8_byte_sequence.cbegin()}; + std::string const valid{"Valid"}; + for (auto end_it{valid_utf8_byte_sequence.cend() - 1}; + valid_utf8_byte_sequence.cbegin() != end_it; + --end_it) + { + std::string const incomplete_byte_sequence{begin_it, end_it}; + + test_str = valid + incomplete_byte_sequence; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + + test_str = incomplete_byte_sequence + valid; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + } + + // Test invalid lead byte + test_str = valid_utf8_byte_sequence; + constexpr char cInvalidLeadByte{'\xFF'}; + test_str.front() = cInvalidLeadByte; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + + // Test invalid continuation bytes + for (size_t idx{1}; idx < valid_utf8_byte_sequence.size(); ++idx) { + test_str = valid_utf8_byte_sequence; + constexpr uint8_t cInvalidContinuationByteMask{0x40}; + test_str.at(idx) |= cInvalidContinuationByteMask; + REQUIRE((false == is_utf8_encoded(test_str))); + REQUIRE((false == validate_and_escape_utf8_string(test_str).has_value())); + } +} + +TEST_CASE("validate_utf8_code_point_ranges", "[utf8_utils]") { + // Test 1 byte encoding code point range + for (auto code_point{clp::cOneByteUtf8CharCodePointLowerBound}; + code_point <= clp::cOneByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(std::string{static_cast(code_point)})); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 1)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 2 byte encoding code point range + for (auto code_point{clp::cTwoByteUtf8CharCodePointLowerBound}; + code_point <= clp::cTwoByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 1))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2)))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 3 byte encoding code point range + for (auto code_point{clp::cThreeByteUtf8CharCodePointLowerBound}; + code_point <= clp::cThreeByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 2))); + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } + + // Test 4 byte encoding code point range + for (auto code_point{clp::cFourByteUtf8CharCodePointLowerBound}; + code_point <= clp::cFourByteUtf8CharCodePointUpperBound; + ++code_point) + { + REQUIRE(is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3))); + } + + // Test 4 byte encoding code point out of range + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + for (auto code_point{clp::cFourByteUtf8CharCodePointUpperBound + 1}; code_point <= 0x1F'FFFF; + ++code_point) + { + REQUIRE((false == is_utf8_encoded(generate_utf8_byte_sequence(code_point, 3)))); + } +} diff --git a/components/core/tools/scripts/deps-download/boost-outcome.json b/components/core/tools/scripts/deps-download/boost-outcome.json deleted file mode 100644 index 01e89b394..000000000 --- a/components/core/tools/scripts/deps-download/boost-outcome.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "url": "https://github.com/boostorg/outcome/archive/refs/tags/boost-1.83.0.zip", - "unzip": true, - "targets": [ - { - "source": "outcome-boost-1.83.0", - "destination": "submodules/boost-outcome" - } - ] -} diff --git a/components/core/tools/scripts/deps-download/download-all.sh b/components/core/tools/scripts/deps-download/download-all.sh index ded2b2612..b552f1247 100755 --- a/components/core/tools/scripts/deps-download/download-all.sh +++ b/components/core/tools/scripts/deps-download/download-all.sh @@ -19,11 +19,11 @@ if [ -e "$project_root_dir/.git" ] ; then git submodule update --init --recursive else python3 "${script_dir}/download-dep.py" "${script_dir}/abseil-cpp.json" - python3 "${script_dir}/download-dep.py" "${script_dir}/boost-outcome.json" python3 "${script_dir}/download-dep.py" "${script_dir}/Catch2.json" python3 "${script_dir}/download-dep.py" "${script_dir}/date.json" python3 "${script_dir}/download-dep.py" "${script_dir}/json.json" python3 "${script_dir}/download-dep.py" "${script_dir}/log-surgeon.json" + python3 "${script_dir}/download-dep.py" "${script_dir}/outcome.json" python3 "${script_dir}/download-dep.py" "${script_dir}/simdjson.json" python3 "${script_dir}/download-dep.py" "${script_dir}/yaml-cpp.json" fi diff --git a/components/core/tools/scripts/deps-download/outcome.json b/components/core/tools/scripts/deps-download/outcome.json new file mode 100644 index 000000000..1a0725f4b --- /dev/null +++ b/components/core/tools/scripts/deps-download/outcome.json @@ -0,0 +1,10 @@ +{ + "url": "https://github.com/ned14/outcome/archive/refs/tags/v2.2.9.zip", + "unzip": true, + "targets": [ + { + "source": "outcome-2.2.9", + "destination": "submodules/outcome" + } + ] +} diff --git a/components/job-orchestration/job_orchestration/executor/search/__init__.py b/components/job-orchestration/job_orchestration/executor/query/__init__.py similarity index 100% rename from components/job-orchestration/job_orchestration/executor/search/__init__.py rename to components/job-orchestration/job_orchestration/executor/query/__init__.py diff --git a/components/job-orchestration/job_orchestration/executor/search/celery.py b/components/job-orchestration/job_orchestration/executor/query/celery.py similarity index 85% rename from components/job-orchestration/job_orchestration/executor/search/celery.py rename to components/job-orchestration/job_orchestration/executor/query/celery.py index 81a36d815..7d09e03ea 100644 --- a/components/job-orchestration/job_orchestration/executor/search/celery.py +++ b/components/job-orchestration/job_orchestration/executor/query/celery.py @@ -2,7 +2,7 @@ from . import celeryconfig -app = Celery("search") +app = Celery("query") app.config_from_object(celeryconfig) if "__main__" == __name__: diff --git a/components/job-orchestration/job_orchestration/executor/search/celeryconfig.py b/components/job-orchestration/job_orchestration/executor/query/celeryconfig.py similarity index 70% rename from components/job-orchestration/job_orchestration/executor/search/celeryconfig.py rename to components/job-orchestration/job_orchestration/executor/query/celeryconfig.py index 3dd99e670..994c0bbcf 100644 --- a/components/job-orchestration/job_orchestration/executor/search/celeryconfig.py +++ b/components/job-orchestration/job_orchestration/executor/query/celeryconfig.py @@ -2,10 +2,14 @@ from job_orchestration.scheduler.constants import QueueName -imports = "job_orchestration.executor.search.fs_search_task" +imports = ( + "job_orchestration.executor.query.fs_search_task", + "job_orchestration.executor.query.extract_ir_task", +) task_routes = { - "job_orchestration.executor.search.fs_search_task.search": QueueName.SEARCH, + "job_orchestration.executor.query.fs_search_task.search": QueueName.QUERY, + "job_orchestration.executor.query.extract_ir_task.extract_ir": QueueName.QUERY, } task_create_missing_queues = True diff --git a/components/job-orchestration/job_orchestration/executor/query/extract_ir_task.py b/components/job-orchestration/job_orchestration/executor/query/extract_ir_task.py new file mode 100644 index 000000000..61fcbf549 --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/extract_ir_task.py @@ -0,0 +1,115 @@ +import datetime +import os +from pathlib import Path +from typing import Any, Dict, List, Optional + +from celery.app.task import Task +from celery.utils.log import get_task_logger +from clp_py_utils.clp_config import Database, StorageEngine +from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.celery import app +from job_orchestration.executor.query.utils import ( + report_command_creation_failure, + run_query_task, +) +from job_orchestration.scheduler.job_config import ExtractIrJobConfig +from job_orchestration.scheduler.scheduler_data import QueryTaskStatus + +# Setup logging +logger = get_task_logger(__name__) + + +def make_command( + storage_engine: str, + clp_home: Path, + archives_dir: Path, + archive_id: str, + ir_output_dir: Path, + extract_ir_config: ExtractIrJobConfig, + results_cache_uri: str, + ir_collection: str, +) -> Optional[List[str]]: + if StorageEngine.CLP == storage_engine: + if not extract_ir_config.file_split_id: + logger.error("file_split_id not supplied") + return None + command = [ + str(clp_home / "bin" / "clo"), + "i", + str(archives_dir / archive_id), + extract_ir_config.file_split_id, + str(ir_output_dir), + results_cache_uri, + ir_collection, + ] + if extract_ir_config.target_uncompressed_size is not None: + command.append("--target-size") + command.append(str(extract_ir_config.target_uncompressed_size)) + else: + logger.error(f"Unsupported storage engine {storage_engine}") + return None + + return command + + +@app.task(bind=True) +def extract_ir( + self: Task, + job_id: str, + task_id: int, + job_config_obj: dict, + archive_id: str, + clp_metadata_db_conn_params: dict, + results_cache_uri: str, +) -> Dict[str, Any]: + task_name = "IR extraction" + + # Setup logging to file + clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) + clp_logging_level = os.getenv("CLP_LOGGING_LEVEL") + set_logging_level(logger, clp_logging_level) + + logger.info(f"Started {task_name} task for job {job_id}") + + start_time = datetime.datetime.now() + task_status: QueryTaskStatus + sql_adapter = SQL_Adapter(Database.parse_obj(clp_metadata_db_conn_params)) + + # Make task_command + clp_home = Path(os.getenv("CLP_HOME")) + archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) + clp_storage_engine = os.getenv("CLP_STORAGE_ENGINE") + ir_output_dir = Path(os.getenv("CLP_IR_OUTPUT_DIR")) + ir_collection = os.getenv("CLP_IR_COLLECTION") + extract_ir_config = ExtractIrJobConfig.parse_obj(job_config_obj) + + task_command = make_command( + storage_engine=clp_storage_engine, + clp_home=clp_home, + archives_dir=archive_directory, + archive_id=archive_id, + ir_output_dir=ir_output_dir, + extract_ir_config=extract_ir_config, + results_cache_uri=results_cache_uri, + ir_collection=ir_collection, + ) + if not task_command: + return report_command_creation_failure( + sql_adapter=sql_adapter, + logger=logger, + task_name=task_name, + task_id=task_id, + start_time=start_time, + ) + + return run_query_task( + sql_adapter=sql_adapter, + logger=logger, + clp_logs_dir=clp_logs_dir, + task_command=task_command, + task_name=task_name, + job_id=job_id, + task_id=task_id, + start_time=start_time, + ) diff --git a/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py new file mode 100644 index 000000000..162056220 --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/fs_search_task.py @@ -0,0 +1,152 @@ +import datetime +import os +from pathlib import Path +from typing import Any, Dict, List, Optional + +from celery.app.task import Task +from celery.utils.log import get_task_logger +from clp_py_utils.clp_config import Database, StorageEngine +from clp_py_utils.clp_logging import set_logging_level +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.celery import app +from job_orchestration.executor.query.utils import ( + report_command_creation_failure, + run_query_task, +) +from job_orchestration.scheduler.job_config import SearchJobConfig +from job_orchestration.scheduler.scheduler_data import QueryTaskStatus + +# Setup logging +logger = get_task_logger(__name__) + + +def make_command( + storage_engine: str, + clp_home: Path, + archives_dir: Path, + archive_id: str, + search_config: SearchJobConfig, + results_cache_uri: str, + results_collection: str, +) -> Optional[List[str]]: + if StorageEngine.CLP == storage_engine: + command = [str(clp_home / "bin" / "clo"), "s", str(archives_dir / archive_id)] + if search_config.path_filter is not None: + command.append("--file-path") + command.append(search_config.path_filter) + elif StorageEngine.CLP_S == storage_engine: + command = [ + str(clp_home / "bin" / "clp-s"), + "s", + str(archives_dir), + "--archive-id", + archive_id, + ] + else: + logger.error(f"Unsupported storage engine {storage_engine}") + return None + + command.append(search_config.query_string) + if search_config.begin_timestamp is not None: + command.append("--tge") + command.append(str(search_config.begin_timestamp)) + if search_config.end_timestamp is not None: + command.append("--tle") + command.append(str(search_config.end_timestamp)) + if search_config.ignore_case: + command.append("--ignore-case") + + if search_config.aggregation_config is not None: + aggregation_config = search_config.aggregation_config + if aggregation_config.do_count_aggregation is not None: + command.append("--count") + if aggregation_config.count_by_time_bucket_size is not None: + command.append("--count-by-time") + command.append(str(aggregation_config.count_by_time_bucket_size)) + + # fmt: off + command.extend(( + "reducer", + "--host", aggregation_config.reducer_host, + "--port", str(aggregation_config.reducer_port), + "--job-id", str(aggregation_config.job_id) + )) + # fmt: on + elif search_config.network_address is not None: + # fmt: off + command.extend(( + "network", + "--host", search_config.network_address[0], + "--port", str(search_config.network_address[1]) + )) + # fmt: on + else: + # fmt: off + command.extend(( + "results-cache", + "--uri", results_cache_uri, + "--collection", results_collection, + "--max-num-results", str(search_config.max_num_results) + )) + # fmt: on + + return command + + +@app.task(bind=True) +def search( + self: Task, + job_id: str, + task_id: int, + job_config_obj: dict, + archive_id: str, + clp_metadata_db_conn_params: dict, + results_cache_uri: str, +) -> Dict[str, Any]: + task_name = "search" + + # Setup logging to file + clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) + clp_logging_level = os.getenv("CLP_LOGGING_LEVEL") + set_logging_level(logger, clp_logging_level) + + logger.info(f"Started {task_name} task for job {job_id}") + + start_time = datetime.datetime.now() + task_status: QueryTaskStatus + sql_adapter = SQL_Adapter(Database.parse_obj(clp_metadata_db_conn_params)) + + # Make task_command + clp_home = Path(os.getenv("CLP_HOME")) + archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) + clp_storage_engine = os.getenv("CLP_STORAGE_ENGINE") + search_config = SearchJobConfig.parse_obj(job_config_obj) + + task_command = make_command( + storage_engine=clp_storage_engine, + clp_home=clp_home, + archives_dir=archive_directory, + archive_id=archive_id, + search_config=search_config, + results_cache_uri=results_cache_uri, + results_collection=job_id, + ) + if not task_command: + return report_command_creation_failure( + sql_adapter=sql_adapter, + logger=logger, + task_name=task_name, + task_id=task_id, + start_time=start_time, + ) + + return run_query_task( + sql_adapter=sql_adapter, + logger=logger, + clp_logs_dir=clp_logs_dir, + task_command=task_command, + task_name=task_name, + job_id=job_id, + task_id=task_id, + start_time=start_time, + ) diff --git a/components/job-orchestration/job_orchestration/executor/query/utils.py b/components/job-orchestration/job_orchestration/executor/query/utils.py new file mode 100644 index 000000000..69d22398e --- /dev/null +++ b/components/job-orchestration/job_orchestration/executor/query/utils.py @@ -0,0 +1,135 @@ +import datetime +import os +import signal +import subprocess +import sys +from contextlib import closing +from logging import Logger +from pathlib import Path +from typing import Any, Dict, List + +from clp_py_utils.clp_config import QUERY_TASKS_TABLE_NAME +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.scheduler.scheduler_data import QueryTaskResult, QueryTaskStatus + + +def get_task_log_file_path(clp_logs_dir: Path, job_id: str, task_id: int) -> Path: + worker_logs_dir = clp_logs_dir / job_id + worker_logs_dir.mkdir(exist_ok=True, parents=True) + return worker_logs_dir / f"{task_id}-clo.log" + + +def report_command_creation_failure( + sql_adapter: SQL_Adapter, + logger: Logger, + task_name: str, + task_id: int, + start_time: datetime.datetime, +): + logger.error(f"Error creating {task_name} command") + task_status = QueryTaskStatus.FAILED + update_query_task_metadata( + sql_adapter, + task_id, + dict(status=task_status, duration=0, start_time=start_time), + ) + + return QueryTaskResult( + task_id=task_id, + status=task_status, + duration=0, + ).dict() + + +def run_query_task( + sql_adapter: SQL_Adapter, + logger: Logger, + clp_logs_dir: Path, + task_command: List[str], + task_name: str, + job_id: str, + task_id: int, + start_time: datetime.datetime, +): + clo_log_path = get_task_log_file_path(clp_logs_dir, job_id, task_id) + clo_log_file = open(clo_log_path, "w") + + task_status = QueryTaskStatus.RUNNING + update_query_task_metadata( + sql_adapter, task_id, dict(status=task_status, start_time=start_time) + ) + + logger.info(f'Running: {" ".join(task_command)}') + task_proc = subprocess.Popen( + task_command, + preexec_fn=os.setpgrp, + close_fds=True, + stdout=clo_log_file, + stderr=clo_log_file, + ) + + def sigterm_handler(_signo, _stack_frame): + logger.debug("Entered sigterm handler") + if task_proc.poll() is None: + logger.debug(f"Trying to kill {task_name} process") + # Kill the process group in case the task process also forked + os.killpg(os.getpgid(task_proc.pid), signal.SIGTERM) + os.waitpid(task_proc.pid, 0) + logger.info(f"Cancelling {task_name} task.") + # Add 128 to follow convention for exit codes from signals + # https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549 + sys.exit(_signo + 128) + + # Register the function to kill the child process at exit + signal.signal(signal.SIGTERM, sigterm_handler) + + logger.info(f"Waiting for {task_name} to finish") + # `communicate` is equivalent to `wait` in this case, but avoids deadlocks if we switch to + # piping stdout/stderr in the future. + task_proc.communicate() + return_code = task_proc.returncode + if 0 != return_code: + task_status = QueryTaskStatus.FAILED + logger.error( + f"{task_name} task {task_id} failed for job {job_id} - return_code={return_code}" + ) + else: + task_status = QueryTaskStatus.SUCCEEDED + logger.info(f"{task_name} task {task_id} completed for job {job_id}") + + clo_log_file.close() + duration = (datetime.datetime.now() - start_time).total_seconds() + + update_query_task_metadata( + sql_adapter, task_id, dict(status=task_status, start_time=start_time, duration=duration) + ) + + task_result = QueryTaskResult( + status=task_status, + task_id=task_id, + duration=duration, + ) + + if QueryTaskStatus.FAILED == task_status: + task_result.error_log_path = str(clo_log_path) + + return task_result.dict() + + +def update_query_task_metadata( + sql_adapter: SQL_Adapter, + task_id: int, + kv_pairs: Dict[str, Any], +): + with closing(sql_adapter.create_connection(True)) as db_conn, closing( + db_conn.cursor(dictionary=True) + ) as db_cursor: + if not kv_pairs or len(kv_pairs) == 0: + raise ValueError("No key-value pairs provided to update query task metadata") + + query = f""" + UPDATE {QUERY_TASKS_TABLE_NAME} + SET {', '.join([f'{k}="{v}"' for k, v in kv_pairs.items()])} + WHERE id = {task_id} + """ + db_cursor.execute(query) diff --git a/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py b/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py deleted file mode 100644 index f6a4a73bc..000000000 --- a/components/job-orchestration/job_orchestration/executor/search/fs_search_task.py +++ /dev/null @@ -1,177 +0,0 @@ -import os -import signal -import subprocess -import sys -from pathlib import Path -from typing import Any, Dict - -from celery.app.task import Task -from celery.utils.log import get_task_logger -from clp_py_utils.clp_config import StorageEngine -from clp_py_utils.clp_logging import set_logging_level -from job_orchestration.executor.search.celery import app -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.scheduler_data import SearchTaskResult - -# Setup logging -logger = get_task_logger(__name__) - - -def make_command( - storage_engine: str, - clp_home: Path, - archives_dir: Path, - archive_id: str, - search_config: SearchConfig, - results_cache_uri: str, - results_collection: str, -): - if StorageEngine.CLP == storage_engine: - command = [str(clp_home / "bin" / "clo"), str(archives_dir / archive_id)] - if search_config.path_filter is not None: - command.append("--file-path") - command.append(search_config.path_filter) - elif StorageEngine.CLP_S == storage_engine: - command = [ - str(clp_home / "bin" / "clp-s"), - "s", - str(archives_dir), - "--archive-id", - archive_id, - ] - else: - raise ValueError(f"Unsupported storage engine {storage_engine}") - - command.append(search_config.query_string) - if search_config.begin_timestamp is not None: - command.append("--tge") - command.append(str(search_config.begin_timestamp)) - if search_config.end_timestamp is not None: - command.append("--tle") - command.append(str(search_config.end_timestamp)) - if search_config.ignore_case: - command.append("--ignore-case") - - if search_config.aggregation_config is not None: - aggregation_config = search_config.aggregation_config - if aggregation_config.do_count_aggregation is not None: - command.append("--count") - if aggregation_config.count_by_time_bucket_size is not None: - command.append("--count-by-time") - command.append(str(aggregation_config.count_by_time_bucket_size)) - - # fmt: off - command.extend(( - "reducer", - "--host", aggregation_config.reducer_host, - "--port", str(aggregation_config.reducer_port), - "--job-id", str(aggregation_config.job_id) - )) - # fmt: on - elif search_config.network_address is not None: - # fmt: off - command.extend(( - "network", - "--host", search_config.network_address[0], - "--port", str(search_config.network_address[1]) - )) - # fmt: on - else: - # fmt: off - command.extend(( - "results-cache", - "--uri", results_cache_uri, - "--collection", results_collection, - "--max-num-results", str(search_config.max_num_results) - )) - # fmt: on - - return command - - -@app.task(bind=True) -def search( - self: Task, - job_id: str, - search_config_obj: dict, - archive_id: str, - results_cache_uri: str, -) -> Dict[str, Any]: - task_id = str(self.request.id) - clp_home = Path(os.getenv("CLP_HOME")) - archive_directory = Path(os.getenv("CLP_ARCHIVE_OUTPUT_DIR")) - clp_logs_dir = Path(os.getenv("CLP_LOGS_DIR")) - clp_logging_level = str(os.getenv("CLP_LOGGING_LEVEL")) - clp_storage_engine = str(os.getenv("CLP_STORAGE_ENGINE")) - - # Setup logging to file - worker_logs_dir = clp_logs_dir / job_id - worker_logs_dir.mkdir(exist_ok=True, parents=True) - set_logging_level(logger, clp_logging_level) - clo_log_path = worker_logs_dir / f"{task_id}-clo.log" - clo_log_file = open(clo_log_path, "w") - - logger.info(f"Started task for job {job_id}") - - search_config = SearchConfig.parse_obj(search_config_obj) - - try: - search_command = make_command( - storage_engine=clp_storage_engine, - clp_home=clp_home, - archives_dir=archive_directory, - archive_id=archive_id, - search_config=search_config, - results_cache_uri=results_cache_uri, - results_collection=job_id, - ) - except ValueError as e: - logger.error(f"Error creating search command: {e}") - return SearchTaskResult( - success=False, - task_id=task_id, - ).dict() - - logger.info(f'Running: {" ".join(search_command)}') - search_successful = False - search_proc = subprocess.Popen( - search_command, - preexec_fn=os.setpgrp, - close_fds=True, - stdout=clo_log_file, - stderr=clo_log_file, - ) - - def sigterm_handler(_signo, _stack_frame): - logger.debug("Entered sigterm handler") - if search_proc.poll() is None: - logger.debug("Trying to kill search process") - # Kill the process group in case the search process also forked - os.killpg(os.getpgid(search_proc.pid), signal.SIGTERM) - os.waitpid(search_proc.pid, 0) - logger.info(f"Cancelling search task.") - # Add 128 to follow convention for exit codes from signals - # https://tldp.org/LDP/abs/html/exitcodes.html#AEN23549 - sys.exit(_signo + 128) - - # Register the function to kill the child process at exit - signal.signal(signal.SIGTERM, sigterm_handler) - - logger.info("Waiting for search to finish") - # communicate is equivalent to wait in this case, but avoids deadlocks if we switch to piping - # stdout/stderr in the future. - search_proc.communicate() - return_code = search_proc.returncode - if 0 != return_code: - logger.error(f"Failed search task for job {job_id} - return_code={return_code}") - else: - search_successful = True - logger.info(f"Search task completed for job {job_id}") - - # Close log files - clo_log_file.close() - - return SearchTaskResult( - success=search_successful, - task_id=task_id, - ).dict() diff --git a/components/job-orchestration/job_orchestration/reducer/reducer.py b/components/job-orchestration/job_orchestration/reducer/reducer.py index cee6692df..7a91ab565 100644 --- a/components/job-orchestration/job_orchestration/reducer/reducer.py +++ b/components/job-orchestration/job_orchestration/reducer/reducer.py @@ -55,8 +55,8 @@ def main(argv: List[str]) -> int: # fmt: off reducer_cmd = [ str(clp_home / "bin" / "reducer-server"), - "--scheduler-host", clp_config.search_scheduler.host, - "--scheduler-port", str(clp_config.search_scheduler.port), + "--scheduler-host", clp_config.query_scheduler.host, + "--scheduler-port", str(clp_config.query_scheduler.port), "--mongodb-uri", clp_config.results_cache.get_uri(), "--upsert-interval", str(parsed_args.upsert_interval), "--reducer-host", clp_config.reducer.host, diff --git a/components/job-orchestration/job_orchestration/scheduler/constants.py b/components/job-orchestration/job_orchestration/scheduler/constants.py index a0c3fe764..131719148 100644 --- a/components/job-orchestration/job_orchestration/scheduler/constants.py +++ b/components/job-orchestration/job_orchestration/scheduler/constants.py @@ -8,7 +8,7 @@ class QueueName: COMPRESSION = "compression" - SEARCH = "search" + QUERY = "query" class CompressionJobStatus(IntEnum): @@ -31,8 +31,8 @@ class CompressionTaskStatus(IntEnum): # When adding new states always add them to the end of this enum -# and make necessary changes in the UI, Search Scheduler, and Reducer -class SearchJobStatus(IntEnum): +# and make necessary changes in the UI, Query Scheduler, and Reducer +class QueryJobStatus(IntEnum): PENDING = 0 RUNNING = auto() SUCCEEDED = auto() @@ -41,8 +41,37 @@ class SearchJobStatus(IntEnum): CANCELLED = auto() @staticmethod - def from_str(label: str) -> SearchJobStatus: - return SearchJobStatus[label.upper()] + def from_str(label: str) -> QueryJobStatus: + return QueryJobStatus[label.upper()] + + def __str__(self) -> str: + return str(self.value) + + def to_str(self) -> str: + return str(self.name) + + +class QueryTaskStatus(IntEnum): + PENDING = 0 + RUNNING = auto() + SUCCEEDED = auto() + FAILED = auto() + CANCELLED = auto() + + @staticmethod + def from_str(label: str) -> QueryTaskStatus: + return QueryTaskStatus[label.upper()] + + def __str__(self) -> str: + return str(self.value) + + def to_str(self) -> str: + return str(self.name) + + +class QueryJobType(IntEnum): + SEARCH_OR_AGGREGATION = 0 + EXTRACT_IR = auto() def __str__(self) -> str: return str(self.value) diff --git a/components/job-orchestration/job_orchestration/scheduler/job_config.py b/components/job-orchestration/job_orchestration/scheduler/job_config.py index 93d4ede4e..e90e2ee7f 100644 --- a/components/job-orchestration/job_orchestration/scheduler/job_config.py +++ b/components/job-orchestration/job_orchestration/scheduler/job_config.py @@ -39,7 +39,17 @@ class AggregationConfig(BaseModel): count_by_time_bucket_size: typing.Optional[int] = None # Milliseconds -class SearchConfig(BaseModel): +class QueryJobConfig(BaseModel): ... + + +class ExtractIrJobConfig(QueryJobConfig): + orig_file_id: str + msg_ix: int + file_split_id: typing.Optional[str] = None + target_uncompressed_size: typing.Optional[int] = None + + +class SearchJobConfig(QueryJobConfig): query_string: str max_num_results: int tags: typing.Optional[typing.List[str]] = None diff --git a/components/job-orchestration/job_orchestration/scheduler/search/__init__.py b/components/job-orchestration/job_orchestration/scheduler/query/__init__.py similarity index 100% rename from components/job-orchestration/job_orchestration/scheduler/search/__init__.py rename to components/job-orchestration/job_orchestration/scheduler/query/__init__.py diff --git a/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py new file mode 100644 index 000000000..2a0f855a3 --- /dev/null +++ b/components/job-orchestration/job_orchestration/scheduler/query/query_scheduler.py @@ -0,0 +1,1027 @@ +""" +A scheduler for scheduling query jobs in the CLP package. + +NOTE: This scheduler currently only has partial handling for failures of the database. Specifically, +in the event that the database is unreachable, the scheduler will continue running as if reads from +the database return no records and writes to the database always fail. Failed writes are currently +silently ignored, in which case the state of the database won't match the scheduler's internal +state. If the database comes back up, the scheduler will eventually reconnect to it and reads/writes +will function as normal again. However, the mismatched state may lead to unexpected behaviour like +jobs seemingly being stuck in the "RUNNING" state or jobs being repeated, which in turn will create +duplicated search results in the results cache, possibly long after the results had already been +cleared from the cache. Unfortunately, these effects will require manual intervention to clean-up. +TODO Address this limitation. +""" + +from __future__ import annotations + +import argparse +import asyncio +import contextlib +import datetime +import logging +import os +import pathlib +import sys +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +import celery +import msgpack +import pymongo +from clp_py_utils.clp_config import ( + CLP_METADATA_TABLE_PREFIX, + CLPConfig, + QUERY_JOBS_TABLE_NAME, + QUERY_TASKS_TABLE_NAME, +) +from clp_py_utils.clp_logging import get_logger, get_logging_formatter, set_logging_level +from clp_py_utils.core import read_yaml_config_file +from clp_py_utils.decorators import exception_default_value +from clp_py_utils.sql_adapter import SQL_Adapter +from job_orchestration.executor.query.extract_ir_task import extract_ir +from job_orchestration.executor.query.fs_search_task import search +from job_orchestration.scheduler.constants import QueryJobStatus, QueryJobType, QueryTaskStatus +from job_orchestration.scheduler.job_config import ExtractIrJobConfig, SearchJobConfig +from job_orchestration.scheduler.query.reducer_handler import ( + handle_reducer_connection, + ReducerHandlerMessage, + ReducerHandlerMessageQueues, + ReducerHandlerMessageType, +) +from job_orchestration.scheduler.scheduler_data import ( + ExtractIrJob, + InternalJobState, + QueryJob, + QueryTaskResult, + SearchJob, +) +from pydantic import ValidationError + +# Setup logging +logger = get_logger("search-job-handler") + +# Dictionary of active jobs indexed by job id +active_jobs: Dict[str, QueryJob] = {} + +# Dictionary that maps IDs of file splits being extracted to IDs of jobs waiting for them +active_file_split_ir_extractions: Dict[str, List[str]] = {} + +reducer_connection_queue: Optional[asyncio.Queue] = None + + +def cancel_job_except_reducer(job: SearchJob): + """ + Cancels the job apart from releasing the reducer since that requires an async call. + NOTE: By keeping this method synchronous, the caller can cancel most of the job atomically, + making it easier to avoid using locks in concurrent tasks. + :param job: + """ + + if InternalJobState.RUNNING == job.state: + job.current_sub_job_async_task_result.revoke(terminate=True) + try: + job.current_sub_job_async_task_result.get() + except Exception: + pass + elif InternalJobState.WAITING_FOR_REDUCER == job.state: + job.reducer_acquisition_task.cancel() + + +async def release_reducer_for_job(job: SearchJob): + """ + Releases the reducer assigned to the given job + :param job: + """ + + if job.reducer_handler_msg_queues is not None: + # Signal the reducer to cancel the job + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + +@exception_default_value(default=[]) +def fetch_new_query_jobs(db_conn) -> list: + """ + Fetches query jobs with status=PENDING from the database. + :param db_conn: + :return: The pending query jobs on success. An empty list if an exception occurs while + interacting with the database. + """ + with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: + db_cursor.execute( + f""" + SELECT {QUERY_JOBS_TABLE_NAME}.id as job_id, + {QUERY_JOBS_TABLE_NAME}.job_config, + {QUERY_JOBS_TABLE_NAME}.type + FROM {QUERY_JOBS_TABLE_NAME} + WHERE {QUERY_JOBS_TABLE_NAME}.status={QueryJobStatus.PENDING} + """ + ) + return db_cursor.fetchall() + + +@exception_default_value(default=[]) +def fetch_cancelling_search_jobs(db_conn) -> list: + """ + Fetches search jobs with status=CANCELLING from the database. + :param db_conn: + :return: The cancelling search jobs on success. An empty list if an exception occurs while + interacting with the database. + """ + with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: + db_cursor.execute( + f""" + SELECT {QUERY_JOBS_TABLE_NAME}.id as job_id + FROM {QUERY_JOBS_TABLE_NAME} + WHERE {QUERY_JOBS_TABLE_NAME}.status={QueryJobStatus.CANCELLING} + AND {QUERY_JOBS_TABLE_NAME}.type={QueryJobType.SEARCH_OR_AGGREGATION} + """ + ) + return db_cursor.fetchall() + + +@exception_default_value(default=False) +def set_job_or_task_status( + db_conn, + table_name: str, + job_id: str, + status: QueryJobStatus | QueryTaskStatus, + prev_status: Optional[QueryJobStatus | QueryTaskStatus] = None, + **kwargs, +) -> bool: + """ + Sets the status of the job or the tasks identified by `job_id` to `status`. If `prev_status` is + specified, the update is conditional on the job/task's current status matching `prev_status`. If + `kwargs` are specified, the fields identified by the args are also updated. + :param db_conn: + :param table_name: + :param job_id: + :param status: + :param prev_status: + :param kwargs: + :return: True on success, False if the update fails or an exception occurs while interacting + with the database. + """ + field_set_expressions = [f"status={status}"] + if QUERY_JOBS_TABLE_NAME == table_name: + id_col_name = "id" + field_set_expressions.extend([f'{k}="{v}"' for k, v in kwargs.items()]) + elif QUERY_TASKS_TABLE_NAME == table_name: + id_col_name = "job_id" + field_set_expressions.extend([f"{k}={v}" for k, v in kwargs.items()]) + else: + raise ValueError(f"Unsupported table name {table_name}") + update = ( + f'UPDATE {table_name} SET {", ".join(field_set_expressions)} WHERE {id_col_name}={job_id}' + ) + + if prev_status is not None: + update += f" AND status={prev_status}" + + with contextlib.closing(db_conn.cursor()) as cursor: + cursor.execute(update) + db_conn.commit() + rval = cursor.rowcount != 0 + return rval + + +async def handle_cancelling_search_jobs(db_conn_pool) -> None: + global active_jobs + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + cancelling_jobs = fetch_cancelling_search_jobs(db_conn) + + for cancelling_job in cancelling_jobs: + job_id = str(cancelling_job["job_id"]) + if job_id in active_jobs: + job = active_jobs.pop(job_id) + cancel_job_except_reducer(job) + # Perform any async tasks last so that it's easier to reason about synchronization + # issues between concurrent tasks + await release_reducer_for_job(job) + else: + continue + + set_job_or_task_status( + db_conn, + QUERY_TASKS_TABLE_NAME, + job_id, + QueryTaskStatus.CANCELLED, + QueryTaskStatus.PENDING, + duration=0, + ) + + set_job_or_task_status( + db_conn, + QUERY_TASKS_TABLE_NAME, + job_id, + QueryTaskStatus.CANCELLED, + QueryTaskStatus.RUNNING, + duration="TIMESTAMPDIFF(MICROSECOND, start_time, NOW())/1000000.0", + ) + + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.CANCELLED, + QueryJobStatus.CANCELLING, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + logger.info(f"Cancelled job {job_id}.") + else: + logger.error(f"Failed to cancel job {job_id}.") + + +def insert_query_tasks_into_db(db_conn, job_id, archive_ids: List[str]) -> List[int]: + task_ids = [] + with contextlib.closing(db_conn.cursor()) as cursor: + for archive_id in archive_ids: + cursor.execute( + f""" + INSERT INTO {QUERY_TASKS_TABLE_NAME} + (job_id, archive_id) + VALUES({job_id}, '{archive_id}') + """ + ) + task_ids.append(cursor.lastrowid) + db_conn.commit() + return task_ids + + +@exception_default_value(default=[]) +def get_archives_for_search( + db_conn, + search_config: SearchJobConfig, +): + query = f"""SELECT id as archive_id, end_timestamp + FROM {CLP_METADATA_TABLE_PREFIX}archives + """ + filter_clauses = [] + if search_config.end_timestamp is not None: + filter_clauses.append(f"begin_timestamp <= {search_config.end_timestamp}") + if search_config.begin_timestamp is not None: + filter_clauses.append(f"end_timestamp >= {search_config.begin_timestamp}") + if search_config.tags is not None: + filter_clauses.append( + f"id IN (SELECT archive_id FROM {CLP_METADATA_TABLE_PREFIX}archive_tags WHERE " + f"tag_id IN (SELECT tag_id FROM {CLP_METADATA_TABLE_PREFIX}tags WHERE tag_name IN " + f"(%s)))" % ", ".join(["%s" for _ in search_config.tags]) + ) + if len(filter_clauses) > 0: + query += " WHERE " + " AND ".join(filter_clauses) + query += " ORDER BY end_timestamp DESC" + + with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: + if search_config.tags is not None: + cursor.execute(query, tuple(search_config.tags)) + else: + cursor.execute(query) + archives_for_search = list(cursor.fetchall()) + return archives_for_search + + +def get_archive_and_file_split_ids_for_extraction( + db_conn, + extract_ir_config: ExtractIrJobConfig, +) -> Tuple[Optional[str], Optional[str]]: + orig_file_id = extract_ir_config.orig_file_id + msg_ix = extract_ir_config.msg_ix + + results = get_archive_and_file_split_ids(db_conn, orig_file_id, msg_ix) + if len(results) == 0: + logger.error(f"No matching file splits for orig_file_id={orig_file_id}, msg_ix={msg_ix}") + return None, None + elif len(results) > 1: + logger.error(f"Multiple file splits found for orig_file_id={orig_file_id}, msg_ix={msg_ix}") + for result in results: + logger.error(f"{result['archive_id']}:{result['id']}") + return None, None + + return results[0]["archive_id"], results[0]["file_split_id"] + + +@exception_default_value(default=[]) +def get_archive_and_file_split_ids( + db_conn, + orig_file_id: str, + msg_ix: int, +): + """ + Fetches the IDs of the file split and the archive containing the file split based on the + following criteria: + 1. The file split's original file id = `orig_file_id` + 2. The file split includes the message with index = `msg_ix` + :param db_conn: + :param orig_file_id: Original file id of the split + :param msg_ix: Index of the message that the file split must include + :return: A list of (archive id, file split id) on success. An empty list if + an exception occurs while interacting with the database. + """ + + query = f"""SELECT archive_id, id as file_split_id + FROM {CLP_METADATA_TABLE_PREFIX}files WHERE + orig_file_id = '{orig_file_id}' AND + begin_message_ix <= {msg_ix} AND + (begin_message_ix + num_messages) > {msg_ix} + """ + + with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: + cursor.execute(query) + results = list(cursor.fetchall()) + return results + + +def get_task_group_for_job( + archive_ids: List[str], + task_ids: List[int], + job: QueryJob, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, +): + job_config_obj = job.get_config().dict() + job_type = job.get_type() + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + return celery.group( + search.s( + job_id=job.id, + archive_id=archive_ids[i], + task_id=task_ids[i], + job_config_obj=job_config_obj, + clp_metadata_db_conn_params=clp_metadata_db_conn_params, + results_cache_uri=results_cache_uri, + ) + for i in range(len(archive_ids)) + ) + elif QueryJobType.EXTRACT_IR == job_type: + return celery.group( + extract_ir.s( + job_id=job.id, + archive_id=archive_ids[i], + task_id=task_ids[i], + job_config_obj=job_config_obj, + clp_metadata_db_conn_params=clp_metadata_db_conn_params, + results_cache_uri=results_cache_uri, + ) + for i in range(len(archive_ids)) + ) + else: + error_msg = f"Unexpected job type: {job_type}" + logger.error(error_msg) + raise NotImplementedError(error_msg) + + +def dispatch_query_job( + db_conn, + job: QueryJob, + archive_ids: List[str], + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, +) -> None: + global active_jobs + task_ids = insert_query_tasks_into_db(db_conn, job.id, archive_ids) + + task_group = get_task_group_for_job( + archive_ids, + task_ids, + job, + clp_metadata_db_conn_params, + results_cache_uri, + ) + job.current_sub_job_async_task_result = task_group.apply_async() + job.state = InternalJobState.RUNNING + + +async def acquire_reducer_for_job(job: SearchJob): + reducer_host: Optional[str] = None + reducer_port: Optional[int] = None + reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] = None + while True: + reducer_host, reducer_port, reducer_handler_msg_queues = ( + await reducer_connection_queue.get() + ) + """ + Below, the task can either be cancelled before sending the job config to the reducer or + before the reducer acknowledges the job. If the task is cancelled before we send the job + config to the reducer, then we have two options: + + 1. Put the reducer's connection info back in the queue for another job to pick up. + 2. Tell the reducer to restart its job handling loop. + + If the task is cancelled after we've sent the job config to the reducer, then we have to + use option (2), so we use option (2) in both cases. + """ + try: + msg = ReducerHandlerMessage( + ReducerHandlerMessageType.AGGREGATION_CONFIG, job.search_config.aggregation_config + ) + await reducer_handler_msg_queues.put_to_handler(msg) + + msg = await reducer_handler_msg_queues.get_from_handler() + if msg.msg_type == ReducerHandlerMessageType.SUCCESS: + break + elif msg.msg_type != ReducerHandlerMessageType.FAILURE: + error_msg = f"Unexpected msg_type: {msg.msg_type.name}" + raise NotImplementedError(error_msg) + except asyncio.CancelledError: + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await reducer_handler_msg_queues.put_to_handler(msg) + raise + + job.reducer_handler_msg_queues = reducer_handler_msg_queues + job.search_config.aggregation_config.reducer_host = reducer_host + job.search_config.aggregation_config.reducer_port = reducer_port + job.state = InternalJobState.WAITING_FOR_DISPATCH + job.reducer_acquisition_task = None + + logger.info(f"Got reducer for job {job.id} at {reducer_host}:{reducer_port}") + + +def dispatch_job_and_update_db( + db_conn, + new_job: QueryJob, + target_archives: List[str], + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + num_tasks: int, +) -> None: + dispatch_query_job( + db_conn, new_job, target_archives, clp_metadata_db_conn_params, results_cache_uri + ) + start_time = datetime.datetime.now() + new_job.start_time = start_time + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + new_job.id, + QueryJobStatus.RUNNING, + QueryJobStatus.PENDING, + start_time=start_time, + num_tasks=num_tasks, + ) + + +def handle_pending_query_jobs( + db_conn_pool, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + ir_collection_name: str, + num_archives_to_search_per_sub_job: int, +) -> List[asyncio.Task]: + global active_jobs + global active_file_split_ir_extractions + + reducer_acquisition_tasks = [] + pending_search_jobs = [ + job + for job in active_jobs.values() + if InternalJobState.WAITING_FOR_DISPATCH == job.state + and job.get_type() == QueryJobType.SEARCH_OR_AGGREGATION + ] + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + for job in fetch_new_query_jobs(db_conn): + job_id = str(job["job_id"]) + job_type = job["type"] + job_config = job["job_config"] + + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + # Avoid double-dispatch when a job is WAITING_FOR_REDUCER + if job_id in active_jobs: + continue + + search_config = SearchJobConfig.parse_obj(msgpack.unpackb(job_config)) + archives_for_search = get_archives_for_search(db_conn, search_config) + if len(archives_for_search) == 0: + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.SUCCEEDED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.info(f"No matching archives, skipping job {job_id}.") + continue + + new_search_job = SearchJob( + id=job_id, + search_config=search_config, + state=InternalJobState.WAITING_FOR_DISPATCH, + num_archives_to_search=len(archives_for_search), + num_archives_searched=0, + remaining_archives_for_search=archives_for_search, + ) + + if search_config.aggregation_config is not None: + new_search_job.search_config.aggregation_config.job_id = int(job_id) + new_search_job.state = InternalJobState.WAITING_FOR_REDUCER + new_search_job.reducer_acquisition_task = asyncio.create_task( + acquire_reducer_for_job(new_search_job) + ) + reducer_acquisition_tasks.append(new_search_job.reducer_acquisition_task) + else: + pending_search_jobs.append(new_search_job) + active_jobs[job_id] = new_search_job + + elif QueryJobType.EXTRACT_IR == job_type: + extract_ir_config = ExtractIrJobConfig.parse_obj(msgpack.unpackb(job_config)) + archive_id, file_split_id = get_archive_and_file_split_ids_for_extraction( + db_conn, extract_ir_config + ) + if not archive_id or not file_split_id: + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.FAILED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.error(f"Failed to set job {job_id} as failed") + continue + + # NOTE: The following two if blocks should not be reordered since if we first check + # whether *an* IR file has been extracted for the requested file split, it doesn't + # mean that *all* IR files have has been extracted for the file split (since the + # extraction job may still be in progress). Thus, we must first check whether the + # file split is in the process of being extracted, and then check whether it's + # already been extracted. + + # Check if the file split is currently being extracted; if so, add the job ID to the + # list of jobs waiting for it. + if file_split_id in active_file_split_ir_extractions: + active_file_split_ir_extractions[file_split_id].append(job_id) + logger.info( + f"Split {file_split_id} is being extracted, so mark job {job_id} as running" + ) + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.RUNNING, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + ): + logger.error(f"Failed to set job {job_id} as running") + continue + + # Check if the file split has already been extracted + if ir_file_exists_for_file_split( + results_cache_uri, ir_collection_name, file_split_id + ): + logger.info( + f"Split {file_split_id} already extracted, so mark job {job_id} as done" + ) + if not set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.SUCCEEDED, + QueryJobStatus.PENDING, + start_time=datetime.datetime.now(), + num_tasks=0, + duration=0, + ): + logger.error(f"Failed to set job {job_id} as succeeded") + continue + + active_file_split_ir_extractions[file_split_id] = [job_id] + extract_ir_config.file_split_id = file_split_id + new_extract_ir_job = ExtractIrJob( + id=job_id, + archive_id=archive_id, + file_split_id=file_split_id, + extract_ir_config=extract_ir_config, + state=InternalJobState.WAITING_FOR_DISPATCH, + ) + target_archive = [new_extract_ir_job.archive_id] + + dispatch_job_and_update_db( + db_conn, + new_extract_ir_job, + target_archive, + clp_metadata_db_conn_params, + results_cache_uri, + 1, + ) + active_jobs[new_extract_ir_job.id] = new_extract_ir_job + logger.info(f"Dispatched IR extraction job {job_id} on archive: {archive_id}") + + else: + # NOTE: We're skipping the job for this iteration, but its status will remain + # unchanged. So this log will print again in the next iteration unless the user + # cancels the job. + logger.error(f"Unexpected job type: {job_type}, skipping job {job_id}") + continue + + for job in pending_search_jobs: + job_id = job.id + if ( + job.search_config.network_address is None + and len(job.remaining_archives_for_search) > num_archives_to_search_per_sub_job + ): + archives_for_search = job.remaining_archives_for_search[ + :num_archives_to_search_per_sub_job + ] + job.remaining_archives_for_search = job.remaining_archives_for_search[ + num_archives_to_search_per_sub_job: + ] + else: + archives_for_search = job.remaining_archives_for_search + job.remaining_archives_for_search = [] + + archive_ids_for_search = [archive["archive_id"] for archive in archives_for_search] + + dispatch_job_and_update_db( + db_conn, + job, + archive_ids_for_search, + clp_metadata_db_conn_params, + results_cache_uri, + job.num_archives_to_search, + ) + logger.info( + f"Dispatched job {job_id} with {len(archive_ids_for_search)} archives to search." + ) + + return reducer_acquisition_tasks + + +def try_getting_task_result(async_task_result): + if not async_task_result.ready(): + return None + return async_task_result.get() + + +def found_max_num_latest_results( + results_cache_uri: str, + job_id: str, + max_num_results: int, + max_timestamp_in_remaining_archives: int, +) -> bool: + with pymongo.MongoClient(results_cache_uri) as results_cache_client: + results_cache_collection = results_cache_client.get_default_database()[job_id] + results_count = results_cache_collection.count_documents({}) + if results_count < max_num_results: + return False + + results = list( + results_cache_collection.find( + projection=["timestamp"], + sort=[("timestamp", pymongo.DESCENDING)], + limit=max_num_results, + ) + .sort("timestamp", pymongo.ASCENDING) + .limit(1) + ) + min_timestamp_in_top_results = 0 if len(results) == 0 else results[0]["timestamp"] + return max_timestamp_in_remaining_archives <= min_timestamp_in_top_results + + +def ir_file_exists_for_file_split( + results_cache_uri: str, ir_collection_name: str, file_split_id: str +): + with pymongo.MongoClient(results_cache_uri) as results_cache_client: + ir_collection = results_cache_client.get_default_database()[ir_collection_name] + results_count = ir_collection.count_documents({"file_split_id": file_split_id}) + return 0 != results_count + + +async def handle_finished_search_job( + db_conn, job: SearchJob, task_results: Optional[Any], results_cache_uri: str +) -> None: + global active_jobs + + job_id = job.id + is_reducer_job = job.reducer_handler_msg_queues is not None + new_job_status = QueryJobStatus.RUNNING + for task_result_obj in task_results: + task_result = QueryTaskResult.parse_obj(task_result_obj) + task_id = task_result.task_id + task_status = task_result.status + if not task_status == QueryTaskStatus.SUCCEEDED: + new_job_status = QueryJobStatus.FAILED + logger.error( + f"Search task job-{job_id}-task-{task_id} failed. " + f"Check {task_result.error_log_path} for details." + ) + else: + job.num_archives_searched += 1 + logger.info( + f"Search task job-{job_id}-task-{task_id} succeeded in " + f"{task_result.duration} second(s)." + ) + + if new_job_status != QueryJobStatus.FAILED: + max_num_results = job.search_config.max_num_results + # Check if we've searched all archives + if len(job.remaining_archives_for_search) == 0: + new_job_status = QueryJobStatus.SUCCEEDED + # Check if we've reached max results + elif False == is_reducer_job and max_num_results > 0: + if found_max_num_latest_results( + results_cache_uri, + job_id, + max_num_results, + job.remaining_archives_for_search[0]["end_timestamp"], + ): + new_job_status = QueryJobStatus.SUCCEEDED + if new_job_status == QueryJobStatus.RUNNING: + job.current_sub_job_async_task_result = None + job.state = InternalJobState.WAITING_FOR_DISPATCH + logger.info(f"Job {job_id} waiting for more archives to search.") + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.RUNNING, + QueryJobStatus.RUNNING, + num_tasks_completed=job.num_archives_searched, + ) + return + + reducer_failed = False + if is_reducer_job: + # Notify reducer that it should have received all results + msg = ReducerHandlerMessage(ReducerHandlerMessageType.SUCCESS) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + msg = await job.reducer_handler_msg_queues.get_from_handler() + if ReducerHandlerMessageType.FAILURE == msg.msg_type: + reducer_failed = True + new_job_status = QueryJobStatus.FAILED + elif ReducerHandlerMessageType.SUCCESS != msg.msg_type: + error_msg = f"Unexpected msg_type: {msg.msg_type.name}" + raise NotImplementedError(error_msg) + + # We set the status regardless of the job's previous status to handle the case where the + # job is cancelled (status = CANCELLING) while we're in this method. + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + new_job_status, + num_tasks_completed=job.num_archives_searched, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + if new_job_status == QueryJobStatus.SUCCEEDED: + logger.info(f"Completed job {job_id}.") + elif reducer_failed: + logger.error(f"Completed job {job_id} with failing reducer.") + else: + logger.info(f"Completed job {job_id} with failing tasks.") + del active_jobs[job_id] + + +async def handle_finished_extract_ir_job( + db_conn, job: ExtractIrJob, task_results: Optional[Any] +) -> None: + global active_jobs + global active_file_split_ir_extractions + + job_id = job.id + file_split_id = job.file_split_id + new_job_status = QueryJobStatus.SUCCEEDED + num_tasks = len(task_results) + if 1 != num_tasks: + logger.error( + f"Unexpected number of tasks for IR extraction job {job_id}. " + f"Expected 1, got {num_tasks}." + ) + new_job_status = QueryJobStatus.FAILED + else: + task_result = QueryTaskResult.parse_obj(task_results[0]) + task_id = task_result.task_id + if not QueryJobStatus.SUCCEEDED == task_result.status: + logger.error( + f"IR extraction task job-{job_id}-task-{task_id} failed. " + f"Check {task_result.error_log_path} for details." + ) + new_job_status = QueryJobStatus.FAILED + else: + logger.info( + f"IR extraction task job-{job_id}-task-{task_id} succeeded in " + f"{task_result.duration} second(s)." + ) + + if set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + new_job_status, + QueryJobStatus.RUNNING, + num_tasks_completed=num_tasks, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ): + if new_job_status == QueryJobStatus.SUCCEEDED: + logger.info(f"Completed IR extraction job {job_id}.") + else: + logger.info(f"Completed IR extraction job {job_id} with failing tasks.") + + waiting_jobs = active_file_split_ir_extractions[file_split_id] + waiting_jobs.remove(job_id) + for waiting_job in waiting_jobs: + logger.info(f"Setting status to {new_job_status.to_str()} for waiting jobs: {waiting_job}.") + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + waiting_job, + new_job_status, + QueryJobStatus.RUNNING, + num_tasks_completed=0, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ) + + del active_file_split_ir_extractions[file_split_id] + del active_jobs[job_id] + + +async def check_job_status_and_update_db(db_conn_pool, results_cache_uri): + global active_jobs + + with contextlib.closing(db_conn_pool.connect()) as db_conn: + for job_id in [ + id for id, job in active_jobs.items() if InternalJobState.RUNNING == job.state + ]: + job = active_jobs[job_id] + try: + returned_results = try_getting_task_result(job.current_sub_job_async_task_result) + except Exception as e: + logger.error(f"Job `{job_id}` failed: {e}.") + # Clean up + if QueryJobType.SEARCH_OR_AGGREGATION == job.get_type(): + if job.reducer_handler_msg_queues is not None: + msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) + await job.reducer_handler_msg_queues.put_to_handler(msg) + + del active_jobs[job_id] + set_job_or_task_status( + db_conn, + QUERY_JOBS_TABLE_NAME, + job_id, + QueryJobStatus.FAILED, + QueryJobStatus.RUNNING, + duration=(datetime.datetime.now() - job.start_time).total_seconds(), + ) + continue + + if returned_results is None: + continue + job_type = job.get_type() + if QueryJobType.SEARCH_OR_AGGREGATION == job_type: + search_job: SearchJob = job + await handle_finished_search_job( + db_conn, search_job, returned_results, results_cache_uri + ) + elif QueryJobType.EXTRACT_IR == job_type: + extract_ir_job: ExtractIrJob = job + await handle_finished_extract_ir_job(db_conn, extract_ir_job, returned_results) + else: + logger.error(f"Unexpected job type: {job_type}, skipping job {job_id}") + + +async def handle_job_updates(db_conn_pool, results_cache_uri: str, jobs_poll_delay: float): + while True: + await handle_cancelling_search_jobs(db_conn_pool) + await check_job_status_and_update_db(db_conn_pool, results_cache_uri) + await asyncio.sleep(jobs_poll_delay) + + +async def handle_jobs( + db_conn_pool, + clp_metadata_db_conn_params: Dict[str, any], + results_cache_uri: str, + ir_collection_name: str, + jobs_poll_delay: float, + num_archives_to_search_per_sub_job: int, +) -> None: + handle_updating_task = asyncio.create_task( + handle_job_updates(db_conn_pool, results_cache_uri, jobs_poll_delay) + ) + + tasks = [handle_updating_task] + while True: + reducer_acquisition_tasks = handle_pending_query_jobs( + db_conn_pool, + clp_metadata_db_conn_params, + results_cache_uri, + ir_collection_name, + num_archives_to_search_per_sub_job, + ) + if 0 == len(reducer_acquisition_tasks): + tasks.append(asyncio.create_task(asyncio.sleep(jobs_poll_delay))) + else: + tasks.extend(reducer_acquisition_tasks) + + done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) + if handle_updating_task in done: + logger.error("handle_job_updates completed unexpectedly.") + try: + handle_updating_task.result() + except Exception: + logger.exception("handle_job_updates failed.") + return + tasks = list(pending) + + +async def main(argv: List[str]) -> int: + global reducer_connection_queue + + args_parser = argparse.ArgumentParser(description="Wait for and run query jobs.") + args_parser.add_argument("--config", "-c", required=True, help="CLP configuration file.") + + parsed_args = args_parser.parse_args(argv[1:]) + + # Setup logging to file + log_file = Path(os.getenv("CLP_LOGS_DIR")) / "query_scheduler.log" + logging_file_handler = logging.FileHandler(filename=log_file, encoding="utf-8") + logging_file_handler.setFormatter(get_logging_formatter()) + logger.addHandler(logging_file_handler) + + # Update logging level based on config + set_logging_level(logger, os.getenv("CLP_LOGGING_LEVEL")) + + # Load configuration + config_path = pathlib.Path(parsed_args.config) + try: + clp_config = CLPConfig.parse_obj(read_yaml_config_file(config_path)) + except ValidationError as err: + logger.error(err) + return -1 + except Exception as ex: + logger.error(ex) + return -1 + + reducer_connection_queue = asyncio.Queue(32) + + sql_adapter = SQL_Adapter(clp_config.database) + + logger.debug(f"Job polling interval {clp_config.query_scheduler.jobs_poll_delay} seconds.") + try: + reducer_handler = await asyncio.start_server( + lambda reader, writer: handle_reducer_connection( + reader, writer, reducer_connection_queue + ), + clp_config.query_scheduler.host, + clp_config.query_scheduler.port, + ) + db_conn_pool = sql_adapter.create_connection_pool( + logger=logger, pool_size=2, disable_localhost_socket_connection=True + ) + + if False == db_conn_pool.alive(): + logger.error( + f"Failed to connect to archive database " + f"{clp_config.database.host}:{clp_config.database.port}." + ) + return -1 + + logger.info( + f"Connected to archive database" + f" {clp_config.database.host}:{clp_config.database.port}." + ) + logger.info("Query scheduler started.") + batch_size = clp_config.query_scheduler.num_archives_to_search_per_sub_job + job_handler = asyncio.create_task( + handle_jobs( + db_conn_pool=db_conn_pool, + clp_metadata_db_conn_params=clp_config.database.get_clp_connection_params_and_type( + True + ), + results_cache_uri=clp_config.results_cache.get_uri(), + ir_collection_name=clp_config.results_cache.ir_collection_name, + jobs_poll_delay=clp_config.query_scheduler.jobs_poll_delay, + num_archives_to_search_per_sub_job=batch_size, + ) + ) + reducer_handler = asyncio.create_task(reducer_handler.serve_forever()) + done, pending = await asyncio.wait( + [job_handler, reducer_handler], return_when=asyncio.FIRST_COMPLETED + ) + if reducer_handler in done: + logger.error("reducer_handler completed unexpectedly.") + try: + reducer_handler.result() + except Exception: + logger.exception("reducer_handler failed.") + if job_handler in done: + logger.error("job_handler completed unexpectedly.") + try: + job_handler.result() + except Exception: + logger.exception("job_handler failed.") + except Exception: + logger.exception(f"Uncaught exception in job handling loop.") + + return 0 + + +if "__main__" == __name__: + sys.exit(asyncio.run(main(sys.argv))) diff --git a/components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py b/components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py similarity index 96% rename from components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py rename to components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py index f25d8afce..5d2ae538b 100644 --- a/components/job-orchestration/job_orchestration/scheduler/search/reducer_handler.py +++ b/components/job-orchestration/job_orchestration/scheduler/query/reducer_handler.py @@ -53,7 +53,7 @@ class _ReducerHandlerWaitState(Enum): JOB_CONFIG = enum.auto() JOB_CONFIG_ACK = enum.auto() - SEARCH_WORKERS_DONE = enum.auto() + QUERY_WORKERS_DONE = enum.auto() REDUCER_DONE = enum.auto() @@ -179,8 +179,8 @@ async def handle_reducer_connection( await msg_queues.put_to_listeners(msg) recv_reducer_msg_task = asyncio.create_task(reader.readexactly(1)) - current_wait_state = _ReducerHandlerWaitState.SEARCH_WORKERS_DONE - elif _ReducerHandlerWaitState.SEARCH_WORKERS_DONE == current_wait_state: + current_wait_state = _ReducerHandlerWaitState.QUERY_WORKERS_DONE + elif _ReducerHandlerWaitState.QUERY_WORKERS_DONE == current_wait_state: if recv_reducer_msg_task in done: await _handle_unexpected_msg_from_reducer(current_wait_state, msg_queues) return @@ -195,7 +195,7 @@ async def handle_reducer_connection( ) return - # Tell the reducer the search workers are done + # Tell the reducer the query workers are done await _send_msg_to_reducer(msgpack.packb({"done": True}), writer) recv_listener_msg_task = asyncio.create_task(msg_queues.get_from_listeners()) diff --git a/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py b/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py index 87c1540e7..5ef92a5d6 100644 --- a/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py +++ b/components/job-orchestration/job_orchestration/scheduler/scheduler_data.py @@ -1,11 +1,20 @@ import asyncio import datetime +from abc import ABC, abstractmethod from enum import auto, Enum from typing import Any, Dict, List, Optional -from job_orchestration.scheduler.constants import CompressionTaskStatus -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.search.reducer_handler import ReducerHandlerMessageQueues +from job_orchestration.scheduler.constants import ( + CompressionTaskStatus, + QueryJobType, + QueryTaskStatus, +) +from job_orchestration.scheduler.job_config import ( + ExtractIrJobConfig, + QueryJobConfig, + SearchJobConfig, +) +from job_orchestration.scheduler.query.reducer_handler import ReducerHandlerMessageQueues from pydantic import BaseModel, validator @@ -35,19 +44,51 @@ class InternalJobState(Enum): RUNNING = auto() -class SearchJob(BaseModel): +class QueryJob(BaseModel, ABC): id: str - search_config: SearchConfig state: InternalJobState - remaining_archives_for_search: List[Dict[str, Any]] + start_time: Optional[datetime.datetime] current_sub_job_async_task_result: Optional[Any] + + @abstractmethod + def get_type(self) -> QueryJobType: ... + + @abstractmethod + def get_config(self) -> QueryJobConfig: ... + + +class ExtractIrJob(QueryJob): + extract_ir_config: ExtractIrJobConfig + file_split_id: str + archive_id: str + + def get_type(self) -> QueryJobType: + return QueryJobType.EXTRACT_IR + + def get_config(self) -> QueryJobConfig: + return self.extract_ir_config + + +class SearchJob(QueryJob): + search_config: SearchJobConfig + num_archives_to_search: int + num_archives_searched: int + remaining_archives_for_search: List[Dict[str, Any]] reducer_acquisition_task: Optional[asyncio.Task] reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] + def get_type(self) -> QueryJobType: + return QueryJobType.SEARCH_OR_AGGREGATION + + def get_config(self) -> QueryJobConfig: + return self.search_config + class Config: # To allow asyncio.Task and asyncio.Queue arbitrary_types_allowed = True -class SearchTaskResult(BaseModel): - success: bool +class QueryTaskResult(BaseModel): + status: QueryTaskStatus task_id: str + duration: float + error_log_path: Optional[str] diff --git a/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py b/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py deleted file mode 100644 index 7f4107d22..000000000 --- a/components/job-orchestration/job_orchestration/scheduler/search/search_scheduler.py +++ /dev/null @@ -1,601 +0,0 @@ -""" -A scheduler for scheduling search jobs in the CLP package. - -NOTE: This scheduler currently only has partial handling for failures of the database. Specifically, -in the event that the database is unreachable, the scheduler will continue running as if reads from -the database return no records and writes to the database always fail. Failed writes are currently -silently ignored, in which case the state of the database won't match the scheduler's internal -state. If the database comes back up, the scheduler will eventually reconnect to it and reads/writes -will function as normal again. However, the mismatched state may lead to unexpected behaviour like -jobs seemingly being stuck in the "RUNNING" state or jobs being repeated, which in turn will create -duplicated search results in the results cache, possibly long after the results had already been -cleared from the cache. Unfortunately, these effects will require manual intervention to clean-up. -TODO Address this limitation. -""" - -import argparse -import asyncio -import contextlib -import logging -import os -import pathlib -import sys -from pathlib import Path -from typing import Dict, List, Optional - -import celery -import msgpack -import pymongo -from clp_py_utils.clp_config import CLP_METADATA_TABLE_PREFIX, CLPConfig, SEARCH_JOBS_TABLE_NAME -from clp_py_utils.clp_logging import get_logger, get_logging_formatter, set_logging_level -from clp_py_utils.core import read_yaml_config_file -from clp_py_utils.decorators import exception_default_value -from clp_py_utils.sql_adapter import SQL_Adapter -from job_orchestration.executor.search.fs_search_task import search -from job_orchestration.scheduler.constants import SearchJobStatus -from job_orchestration.scheduler.job_config import SearchConfig -from job_orchestration.scheduler.scheduler_data import InternalJobState, SearchJob, SearchTaskResult -from job_orchestration.scheduler.search.reducer_handler import ( - handle_reducer_connection, - ReducerHandlerMessage, - ReducerHandlerMessageQueues, - ReducerHandlerMessageType, -) -from pydantic import ValidationError - -# Setup logging -logger = get_logger("search-job-handler") - -# Dictionary of active jobs indexed by job id -active_jobs: Dict[str, SearchJob] = {} - -reducer_connection_queue: Optional[asyncio.Queue] = None - - -def cancel_job_except_reducer(job: SearchJob): - """ - Cancels the job apart from releasing the reducer since that requires an async call. - NOTE: By keeping this method synchronous, the caller can cancel most of the job atomically, - making it easier to avoid using locks in concurrent tasks. - :param job: - """ - - if InternalJobState.RUNNING == job.state: - job.current_sub_job_async_task_result.revoke(terminate=True) - try: - job.current_sub_job_async_task_result.get() - except Exception: - pass - elif InternalJobState.WAITING_FOR_REDUCER == job.state: - job.reducer_acquisition_task.cancel() - - -async def release_reducer_for_job(job: SearchJob): - """ - Releases the reducer assigned to the given job - :param job: - """ - - if job.reducer_handler_msg_queues is not None: - # Signal the reducer to cancel the job - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await job.reducer_handler_msg_queues.put_to_handler(msg) - - -@exception_default_value(default=[]) -def fetch_new_search_jobs(db_conn) -> list: - """ - Fetches search jobs with status=PENDING from the database. - :param db_conn: - :return: The pending search jobs on success. An empty list if an exception occurs while - interacting with the database. - """ - with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: - db_cursor.execute( - f""" - SELECT {SEARCH_JOBS_TABLE_NAME}.id as job_id, - {SEARCH_JOBS_TABLE_NAME}.search_config - FROM {SEARCH_JOBS_TABLE_NAME} - WHERE {SEARCH_JOBS_TABLE_NAME}.status={SearchJobStatus.PENDING} - """ - ) - return db_cursor.fetchall() - - -@exception_default_value(default=[]) -def fetch_cancelling_search_jobs(db_conn) -> list: - """ - Fetches search jobs with status=CANCELLING from the database. - :param db_conn: - :return: The cancelling search jobs on success. An empty list if an exception occurs while - interacting with the database. - """ - with contextlib.closing(db_conn.cursor(dictionary=True)) as db_cursor: - db_cursor.execute( - f""" - SELECT {SEARCH_JOBS_TABLE_NAME}.id as job_id - FROM {SEARCH_JOBS_TABLE_NAME} - WHERE {SEARCH_JOBS_TABLE_NAME}.status={SearchJobStatus.CANCELLING} - """ - ) - return db_cursor.fetchall() - - -@exception_default_value(default=False) -def set_job_status( - db_conn, - job_id: str, - status: SearchJobStatus, - prev_status: Optional[SearchJobStatus] = None, - **kwargs, -) -> bool: - """ - Sets the status of the job identified by `job_id` to `status`. If `prev_status` is specified, - the update is conditional on the job's current status matching `prev_status`. If `kwargs` are - specified, the fields identified by the args are also updated. - :param db_conn: - :param job_id: - :param status: - :param prev_status: - :param kwargs: - :return: True on success, False if the update fails or an exception occurs while interacting - with the database. - """ - field_set_expressions = [f'{k}="{v}"' for k, v in kwargs.items()] - field_set_expressions.append(f"status={status}") - update = ( - f'UPDATE {SEARCH_JOBS_TABLE_NAME} SET {", ".join(field_set_expressions)} WHERE id={job_id}' - ) - - if prev_status is not None: - update += f" AND status={prev_status}" - - with contextlib.closing(db_conn.cursor()) as cursor: - cursor.execute(update) - db_conn.commit() - rval = cursor.rowcount != 0 - return rval - - -async def handle_cancelling_search_jobs(db_conn_pool) -> None: - global active_jobs - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - cancelling_jobs = fetch_cancelling_search_jobs(db_conn) - - for cancelling_job in cancelling_jobs: - job_id = str(cancelling_job["job_id"]) - if job_id in active_jobs: - job = active_jobs.pop(job_id) - cancel_job_except_reducer(job) - # Perform any async tasks last so that it's easier to reason about synchronization - # issues between concurrent tasks - await release_reducer_for_job(job) - else: - continue - if set_job_status( - db_conn, job_id, SearchJobStatus.CANCELLED, prev_status=SearchJobStatus.CANCELLING - ): - logger.info(f"Cancelled job {job_id}.") - else: - logger.error(f"Failed to cancel job {job_id}.") - - -@exception_default_value(default=[]) -def get_archives_for_search( - db_conn, - search_config: SearchConfig, -): - query = f"""SELECT id as archive_id, end_timestamp - FROM {CLP_METADATA_TABLE_PREFIX}archives - """ - filter_clauses = [] - if search_config.end_timestamp is not None: - filter_clauses.append(f"begin_timestamp <= {search_config.end_timestamp}") - if search_config.begin_timestamp is not None: - filter_clauses.append(f"end_timestamp >= {search_config.begin_timestamp}") - if search_config.tags is not None: - filter_clauses.append( - f"id IN (SELECT archive_id FROM {CLP_METADATA_TABLE_PREFIX}archive_tags WHERE " - f"tag_id IN (SELECT tag_id FROM {CLP_METADATA_TABLE_PREFIX}tags WHERE tag_name IN " - f"(%s)))" % ", ".join(["%s" for _ in search_config.tags]) - ) - if len(filter_clauses) > 0: - query += " WHERE " + " AND ".join(filter_clauses) - query += " ORDER BY end_timestamp DESC" - - with contextlib.closing(db_conn.cursor(dictionary=True)) as cursor: - if search_config.tags is not None: - cursor.execute(query, tuple(search_config.tags)) - else: - cursor.execute(query) - archives_for_search = list(cursor.fetchall()) - return archives_for_search - - -def get_task_group_for_job( - archives_for_search: List[Dict[str, any]], - job_id: str, - search_config: SearchConfig, - results_cache_uri: str, -): - search_config_obj = search_config.dict() - return celery.group( - search.s( - job_id=job_id, - archive_id=archive["archive_id"], - search_config_obj=search_config_obj, - results_cache_uri=results_cache_uri, - ) - for archive in archives_for_search - ) - - -def dispatch_search_job( - job: SearchJob, - archives_for_search: List[Dict[str, any]], - results_cache_uri: str, -) -> None: - global active_jobs - task_group = get_task_group_for_job( - archives_for_search, job.id, job.search_config, results_cache_uri - ) - job.current_sub_job_async_task_result = task_group.apply_async() - job.state = InternalJobState.RUNNING - - -async def acquire_reducer_for_job(job: SearchJob): - reducer_host: Optional[str] = None - reducer_port: Optional[int] = None - reducer_handler_msg_queues: Optional[ReducerHandlerMessageQueues] = None - while True: - reducer_host, reducer_port, reducer_handler_msg_queues = ( - await reducer_connection_queue.get() - ) - """ - Below, the task can either be cancelled before sending the job config to the reducer or - before the reducer acknowledges the job. If the task is cancelled before we send the job - config to the reducer, then we have two options: - - 1. Put the reducer's connection info back in the queue for another job to pick up. - 2. Tell the reducer to restart its job handling loop. - - If the task is cancelled after we've sent the job config to the reducer, then we have to - use option (2), so we use option (2) in both cases. - """ - try: - msg = ReducerHandlerMessage( - ReducerHandlerMessageType.AGGREGATION_CONFIG, job.search_config.aggregation_config - ) - await reducer_handler_msg_queues.put_to_handler(msg) - - msg = await reducer_handler_msg_queues.get_from_handler() - if msg.msg_type == ReducerHandlerMessageType.SUCCESS: - break - elif msg.msg_type != ReducerHandlerMessageType.FAILURE: - error_msg = f"Unexpected msg_type: {msg.msg_type.name}" - raise NotImplementedError(error_msg) - except asyncio.CancelledError: - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await reducer_handler_msg_queues.put_to_handler(msg) - raise - - job.reducer_handler_msg_queues = reducer_handler_msg_queues - job.search_config.aggregation_config.reducer_host = reducer_host - job.search_config.aggregation_config.reducer_port = reducer_port - job.state = InternalJobState.WAITING_FOR_DISPATCH - job.reducer_acquisition_task = None - - logger.info(f"Got reducer for job {job.id} at {reducer_host}:{reducer_port}") - - -def handle_pending_search_jobs( - db_conn_pool, results_cache_uri: str, num_archives_to_search_per_sub_job: int -) -> List[asyncio.Task]: - global active_jobs - - reducer_acquisition_tasks = [] - - pending_jobs = [ - job for job in active_jobs.values() if InternalJobState.WAITING_FOR_DISPATCH == job.state - ] - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - for job in fetch_new_search_jobs(db_conn): - job_id = str(job["job_id"]) - - # Avoid double-dispatch when a job is WAITING_FOR_REDUCER - if job_id in active_jobs: - continue - - search_config = SearchConfig.parse_obj(msgpack.unpackb(job["search_config"])) - archives_for_search = get_archives_for_search(db_conn, search_config) - if len(archives_for_search) == 0: - if set_job_status( - db_conn, job_id, SearchJobStatus.SUCCEEDED, SearchJobStatus.PENDING - ): - logger.info(f"No matching archives, skipping job {job['job_id']}.") - continue - - new_search_job = SearchJob( - id=job_id, - search_config=search_config, - state=InternalJobState.WAITING_FOR_DISPATCH, - remaining_archives_for_search=archives_for_search, - ) - - if search_config.aggregation_config is not None: - new_search_job.search_config.aggregation_config.job_id = job["job_id"] - new_search_job.state = InternalJobState.WAITING_FOR_REDUCER - new_search_job.reducer_acquisition_task = asyncio.create_task( - acquire_reducer_for_job(new_search_job) - ) - reducer_acquisition_tasks.append(new_search_job.reducer_acquisition_task) - else: - pending_jobs.append(new_search_job) - active_jobs[job_id] = new_search_job - - for job in pending_jobs: - job_id = job.id - - if ( - job.search_config.network_address is None - and len(job.remaining_archives_for_search) > num_archives_to_search_per_sub_job - ): - archives_for_search = job.remaining_archives_for_search[ - :num_archives_to_search_per_sub_job - ] - job.remaining_archives_for_search = job.remaining_archives_for_search[ - num_archives_to_search_per_sub_job: - ] - else: - archives_for_search = job.remaining_archives_for_search - job.remaining_archives_for_search = [] - - dispatch_search_job(job, archives_for_search, results_cache_uri) - logger.info( - f"Dispatched job {job_id} with {len(archives_for_search)} archives to search." - ) - set_job_status(db_conn, job_id, SearchJobStatus.RUNNING, SearchJobStatus.PENDING) - - return reducer_acquisition_tasks - - -def try_getting_task_result(async_task_result): - if not async_task_result.ready(): - return None - return async_task_result.get() - - -def found_max_num_latest_results( - results_cache_uri: str, - job_id: str, - max_num_results: int, - max_timestamp_in_remaining_archives: int, -) -> bool: - with pymongo.MongoClient(results_cache_uri) as results_cache_client: - results_cache_collection = results_cache_client.get_default_database()[job_id] - results_count = results_cache_collection.count_documents({}) - if results_count < max_num_results: - return False - - results = list( - results_cache_collection.find( - projection=["timestamp"], - sort=[("timestamp", pymongo.DESCENDING)], - limit=max_num_results, - ) - .sort("timestamp", pymongo.ASCENDING) - .limit(1) - ) - min_timestamp_in_top_results = 0 if len(results) == 0 else results[0]["timestamp"] - return max_timestamp_in_remaining_archives <= min_timestamp_in_top_results - - -async def check_job_status_and_update_db(db_conn_pool, results_cache_uri): - global active_jobs - - with contextlib.closing(db_conn_pool.connect()) as db_conn: - for job_id in [ - id for id, job in active_jobs.items() if InternalJobState.RUNNING == job.state - ]: - job = active_jobs[job_id] - is_reducer_job = job.reducer_handler_msg_queues is not None - - try: - returned_results = try_getting_task_result(job.current_sub_job_async_task_result) - except Exception as e: - logger.error(f"Job `{job_id}` failed: {e}.") - # Clean up - if is_reducer_job: - msg = ReducerHandlerMessage(ReducerHandlerMessageType.FAILURE) - await job.reducer_handler_msg_queues.put_to_handler(msg) - del active_jobs[job_id] - set_job_status(db_conn, job_id, SearchJobStatus.FAILED, SearchJobStatus.RUNNING) - continue - - if returned_results is None: - continue - - new_job_status = SearchJobStatus.RUNNING - for task_result_obj in returned_results: - task_result = SearchTaskResult.parse_obj(task_result_obj) - if not task_result.success: - task_id = task_result.task_id - new_job_status = SearchJobStatus.FAILED - logger.debug(f"Task {task_id} failed - result {task_result}.") - - if new_job_status != SearchJobStatus.FAILED: - max_num_results = job.search_config.max_num_results - # Check if we've searched all archives - if len(job.remaining_archives_for_search) == 0: - new_job_status = SearchJobStatus.SUCCEEDED - # Check if we've reached max results - elif False == is_reducer_job and max_num_results > 0: - if found_max_num_latest_results( - results_cache_uri, - job_id, - max_num_results, - job.remaining_archives_for_search[0]["end_timestamp"], - ): - new_job_status = SearchJobStatus.SUCCEEDED - if new_job_status == SearchJobStatus.RUNNING: - job.current_sub_job_async_task_result = None - job.state = InternalJobState.WAITING_FOR_DISPATCH - logger.info(f"Job {job_id} waiting for more archives to search.") - continue - - reducer_failed = False - if is_reducer_job: - # Notify reducer that it should have received all results - msg = ReducerHandlerMessage(ReducerHandlerMessageType.SUCCESS) - await job.reducer_handler_msg_queues.put_to_handler(msg) - - msg = await job.reducer_handler_msg_queues.get_from_handler() - if ReducerHandlerMessageType.FAILURE == msg.msg_type: - reducer_failed = True - new_job_status = SearchJobStatus.FAILED - elif ReducerHandlerMessageType.SUCCESS != msg.msg_type: - error_msg = f"Unexpected msg_type: {msg.msg_type.name}" - raise NotImplementedError(error_msg) - - # We set the status regardless of the job's previous status to handle the case where the - # job is cancelled (status = CANCELLING) while we're in this method. - if set_job_status(db_conn, job_id, new_job_status): - if new_job_status == SearchJobStatus.SUCCEEDED: - logger.info(f"Completed job {job_id}.") - elif reducer_failed: - logger.error(f"Completed job {job_id} with failing reducer.") - else: - logger.info(f"Completed job {job_id} with failing tasks.") - del active_jobs[job_id] - - -async def handle_job_updates(db_conn_pool, results_cache_uri: str, jobs_poll_delay: float): - while True: - await handle_cancelling_search_jobs(db_conn_pool) - await check_job_status_and_update_db(db_conn_pool, results_cache_uri) - await asyncio.sleep(jobs_poll_delay) - - -async def handle_jobs( - db_conn_pool, - results_cache_uri: str, - jobs_poll_delay: float, - num_archives_to_search_per_sub_job: int, -) -> None: - handle_updating_task = asyncio.create_task( - handle_job_updates(db_conn_pool, results_cache_uri, jobs_poll_delay) - ) - - tasks = [handle_updating_task] - while True: - reducer_acquisition_tasks = handle_pending_search_jobs( - db_conn_pool, results_cache_uri, num_archives_to_search_per_sub_job - ) - if 0 == len(reducer_acquisition_tasks): - tasks.append(asyncio.create_task(asyncio.sleep(jobs_poll_delay))) - else: - tasks.extend(reducer_acquisition_tasks) - - done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) - if handle_updating_task in done: - logger.error("handle_job_updates completed unexpectedly.") - try: - handle_updating_task.result() - except Exception: - logger.exception("handle_job_updates failed.") - return - tasks = list(pending) - - -async def main(argv: List[str]) -> int: - global reducer_connection_queue - - args_parser = argparse.ArgumentParser(description="Wait for and run search jobs.") - args_parser.add_argument("--config", "-c", required=True, help="CLP configuration file.") - - parsed_args = args_parser.parse_args(argv[1:]) - - # Setup logging to file - log_file = Path(os.getenv("CLP_LOGS_DIR")) / "search_scheduler.log" - logging_file_handler = logging.FileHandler(filename=log_file, encoding="utf-8") - logging_file_handler.setFormatter(get_logging_formatter()) - logger.addHandler(logging_file_handler) - - # Update logging level based on config - set_logging_level(logger, os.getenv("CLP_LOGGING_LEVEL")) - - # Load configuration - config_path = pathlib.Path(parsed_args.config) - try: - clp_config = CLPConfig.parse_obj(read_yaml_config_file(config_path)) - except ValidationError as err: - logger.error(err) - return -1 - except Exception as ex: - logger.error(ex) - return -1 - - reducer_connection_queue = asyncio.Queue(32) - - sql_adapter = SQL_Adapter(clp_config.database) - - logger.debug(f"Job polling interval {clp_config.search_scheduler.jobs_poll_delay} seconds.") - try: - reducer_handler = await asyncio.start_server( - lambda reader, writer: handle_reducer_connection( - reader, writer, reducer_connection_queue - ), - clp_config.search_scheduler.host, - clp_config.search_scheduler.port, - ) - db_conn_pool = sql_adapter.create_connection_pool( - logger=logger, pool_size=2, disable_localhost_socket_connection=True - ) - - if False == db_conn_pool.alive(): - logger.error( - f"Failed to connect to archive database " - f"{clp_config.database.host}:{clp_config.database.port}." - ) - return -1 - - logger.info( - f"Connected to archive database" - f" {clp_config.database.host}:{clp_config.database.port}." - ) - logger.info("Search scheduler started.") - batch_size = clp_config.search_scheduler.num_archives_to_search_per_sub_job - job_handler = asyncio.create_task( - handle_jobs( - db_conn_pool=db_conn_pool, - results_cache_uri=clp_config.results_cache.get_uri(), - jobs_poll_delay=clp_config.search_scheduler.jobs_poll_delay, - num_archives_to_search_per_sub_job=batch_size, - ) - ) - reducer_handler = asyncio.create_task(reducer_handler.serve_forever()) - done, pending = await asyncio.wait( - [job_handler, reducer_handler], return_when=asyncio.FIRST_COMPLETED - ) - if reducer_handler in done: - logger.error("reducer_handler completed unexpectedly.") - try: - reducer_handler.result() - except Exception: - logger.exception("reducer_handler failed.") - if job_handler in done: - logger.error("job_handler completed unexpectedly.") - try: - job_handler.result() - except Exception: - logger.exception("job_handler failed.") - except Exception: - logger.exception(f"Uncaught exception in job handling loop.") - - return 0 - - -if "__main__" == __name__: - sys.exit(asyncio.run(main(sys.argv))) diff --git a/components/log-viewer-webui/.gitignore b/components/log-viewer-webui/.gitignore new file mode 100644 index 000000000..b6448e2f6 --- /dev/null +++ b/components/log-viewer-webui/.gitignore @@ -0,0 +1,2 @@ +# Dependencies +node_modules diff --git a/components/log-viewer-webui/README.md b/components/log-viewer-webui/README.md new file mode 100644 index 000000000..e052ec9d9 --- /dev/null +++ b/components/log-viewer-webui/README.md @@ -0,0 +1,9 @@ +# Log Viewer WebUI + +A webapp that allows us to serve the [log-viewer] and integrate it with CLP's [webui]. + +See the [docs] for more details. + +[docs]: https://docs.yscope.com/clp/main/dev-guide/components-log-viewer-webui +[log-viewer]: https://github.com/y-scope/yscope-log-viewer +[webui]: ../webui diff --git a/components/log-viewer-webui/client/.gitignore b/components/log-viewer-webui/client/.gitignore new file mode 100644 index 000000000..0fc763fdd --- /dev/null +++ b/components/log-viewer-webui/client/.gitignore @@ -0,0 +1,9 @@ +# Dependencies +/node_modules + +# Build +/dist + +# IDEs +/.idea +/.vscode diff --git a/components/log-viewer-webui/client/package-lock.json b/components/log-viewer-webui/client/package-lock.json new file mode 100644 index 000000000..4141c89aa --- /dev/null +++ b/components/log-viewer-webui/client/package-lock.json @@ -0,0 +1,11041 @@ +{ + "name": "log-viewer-webui-client", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui-client", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/joy": "^5.0.0-beta.48", + "@types/react": "^18.3.3", + "axios": "^1.7.2", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "babel-loader": "^9.1.3", + "css-loader": "^7.1.2", + "eslint-config-yscope": "latest", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.0", + "react-refresh": "^0.14.2", + "style-loader": "^4.0.0", + "webpack": "^5.92.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.7.tgz", + "integrity": "sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.7.tgz", + "integrity": "sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helpers": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.7.tgz", + "integrity": "sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==", + "dependencies": { + "@babel/types": "^7.24.7", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz", + "integrity": "sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz", + "integrity": "sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz", + "integrity": "sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz", + "integrity": "sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.7.tgz", + "integrity": "sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", + "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.7", + "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.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.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-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-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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "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-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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.24.7.tgz", + "integrity": "sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-development": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.24.7.tgz", + "integrity": "sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==", + "dev": true, + "dependencies": { + "@babel/plugin-transform-react-jsx": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-pure-annotations": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.24.7.tgz", + "integrity": "sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", + "@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.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.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-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@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/preset-react": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.24.7.tgz", + "integrity": "sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-react-display-name": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.24.7", + "@babel/plugin-transform-react-jsx-development": "^7.24.7", + "@babel/plugin-transform-react-pure-annotations": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.7.tgz", + "integrity": "sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.7.tgz", + "integrity": "sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.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/@emotion/babel-plugin": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.12.0.tgz", + "integrity": "sha512-y2WQb+oP8Jqvvclh8Q55gLUyb7UFvgv7eJfsj7td5TToBrIUtPay2kMrZi4xjq9qw2vD0ZR5fSho0yqoFgX7Rw==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.2.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/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/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.0.tgz", + "integrity": "sha512-hPV345J/tH0Cwk2wnU/3PBzORQ9HeX+kQSbwI+jslzpRCHE6fSGTohswksA/Ensr8znPzwfzKZCmAM9Lmlhp7g==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.0.tgz", + "integrity": "sha512-SHetuSLvJDzuNbOdtPVbq6yMMMlLoW5Q94uDqJZqy50gcmAjxFkVqmzqSGEFq9gT2iMuIeKV1PXVWmvUhuZLlQ==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz", + "integrity": "sha512-WkL+bw1REC2VNV1goQyfxjx1GYJkcc23CRQkXX+vZNLINyfI7o+uUn/rTGPt/xJ3bJHd5GcljgnxHf4wRw5VWQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/cache": "^11.13.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz", + "integrity": "sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.9.0", + "@emotion/utils": "^1.4.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.0.tgz", + "integrity": "sha512-tkzkY7nQhW/zC4hztlwucpT8QEZ6eUzpXDRhww/Eej4tFfO0FxQYWRyg/c5CCXa4d/f174kqeXYjuQRnhzf6dA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.12.0", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.0" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz", + "integrity": "sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz", + "integrity": "sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", + "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.5", + "@types/estree": "^1.0.5", + "@typescript-eslint/types": "^7.2.0", + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "dev": true, + "peer": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "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, + "peer": 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.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "peer": 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, + "peer": 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/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.6.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.5.tgz", + "integrity": "sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA==", + "dependencies": { + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.8.tgz", + "integrity": "sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q==", + "dependencies": { + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.5" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz", + "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.5.tgz", + "integrity": "sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "peer": 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/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/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "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.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz", + "integrity": "sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.2.0.tgz", + "integrity": "sha512-4B8B+3vFsY4eo33DMKyJPlQ3sBMpPFUZK2dr3O3rXrOGKKbYG44J0XSFkDo1VOQiri5HFEhIeVvItjR2xcazmg==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40.tgz", + "integrity": "sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.14", + "@mui/utils": "^5.15.14", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz", + "integrity": "sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/joy": { + "version": "5.0.0-beta.48", + "resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.48.tgz", + "integrity": "sha512-OhTvjuGl9I5IvpBr0BQyDehIW/xb2yteW6YglHJMdOb/279nItn76X1NBtPV9ImldNlBjReGwvpOXmBTTGER9w==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40", + "@mui/core-downloads-tracker": "^5.16.1", + "@mui/system": "^5.16.1", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.1", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.16.4.tgz", + "integrity": "sha512-ZsAm8cq31SJ37SVWLRlu02v9SRthxnfQofaiv14L5Bht51B0dz6yQEoVU/V8UduZDCCIrWkBHuReVfKhE/UuXA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.16.4", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.4.tgz", + "integrity": "sha512-0+mnkf+UiAmTVB8PZFqOhqf729Yh0Cxq29/5cA3VAyDVTRIUUQ8FXQhiAhUIbijFmM72rY80ahFPXIm4WDbzcA==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.11.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.16.4.tgz", + "integrity": "sha512-ET1Ujl2/8hbsD611/mqUuNArMCGv/fIWO/f8B3ZqF5iyPHM2aS74vhTNyjytncc4i6dYwGxNk+tLa7GwjNS0/w==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.16.4", + "@mui/styled-engine": "^5.16.4", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.4", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.15", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.15.tgz", + "integrity": "sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.16.4", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.4.tgz", + "integrity": "sha512-nlppYwq10TBIFqp7qxY0SvbACOXeOjeVL3pOcDsK0FT8XjrEXh9/+lkg8AEIzD16z7YfiJDQjaJG2OLkE7BxNg==", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "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/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.15.tgz", + "integrity": "sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==", + "dev": true, + "dependencies": { + "ansi-html": "^0.0.9", + "core-js-pure": "^3.23.3", + "error-stack-parser": "^2.0.6", + "html-entities": "^2.1.0", + "loader-utils": "^2.0.4", + "schema-utils": "^4.2.0", + "source-map": "^0.7.3" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "@types/webpack": "4.x || 5.x", + "react-refresh": ">=0.10.0 <1.0.0", + "sockjs-client": "^1.4.0", + "type-fest": ">=0.17.0 <5.0.0", + "webpack": ">=4.43.0 <6.0.0", + "webpack-dev-server": "3.x || 4.x || 5.x", + "webpack-hot-middleware": "2.x", + "webpack-plugin-serve": "0.x || 1.x" + }, + "peerDependenciesMeta": { + "@types/webpack": { + "optional": true + }, + "sockjs-client": { + "optional": true + }, + "type-fest": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + }, + "webpack-hot-middleware": { + "optional": true + }, + "webpack-plugin-serve": { + "optional": true + } + } + }, + "node_modules/@pmmmwh/react-refresh-webpack-plugin/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/@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/@stylistic/eslint-plugin-js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.1.tgz", + "integrity": "sha512-c5c2C8Mos5tTQd+NWpqwEu7VT6SSRooAguFPMj1cp2RkTYl1ynKoXo8MWy3k4rkbzoeYHrqC2UlUzsroAN7wtQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.8.1.tgz", + "integrity": "sha512-k1Eb6rcjMP+mmjvj+vd9y5KUdWn1OBkkPLHXhsrHt5lCDFZxJEs0aVQzE5lpYrtVZVkpc5esTtss/cPJux0lfA==", + "dev": true, + "peer": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^1.8.1", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.8.1.tgz", + "integrity": "sha512-4+40H3lHYTN8OWz+US8CamVkO+2hxNLp9+CAjorI7top/lHqemhpJvKA1LD9Uh+WMY9DYWiWpL2+SZ2wAXY9fQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "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/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "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/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.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", + "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/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, + "peer": true + }, + "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/node": { + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.12", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "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/react": { + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "peer": 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.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "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/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/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "peer": 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.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.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/typescript-estree/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "peer": 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.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "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/utils/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "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, + "peer": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "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.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "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.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "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.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@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.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@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.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "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/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.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", + "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, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "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/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-formats/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/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/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/ansi-html": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.9.tgz", + "integrity": "sha512-ozbS3LuenHVxNRh/wdnN16QapUHzauqSomAl1jwwJRRsGwFwtj644lIhxfWu0Fy0acCij2+AEgHvjscq3dlVXg==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, + "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, + "peer": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "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, + "peer": 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, + "peer": 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.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.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/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/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/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "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/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/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/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "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/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/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.16" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001640", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz", + "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "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/chalk/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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-css": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", + "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 10.0" + } + }, + "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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "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/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/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "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/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/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, + "peer": true + }, + "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/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": "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/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "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/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "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": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-loader/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "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/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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, + "peer": true + }, + "node_modules/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "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/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/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, + "peer": 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==", + "engines": { + "node": ">=0.4.0" + } + }, + "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/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/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/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, + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.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/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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/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/electron-to-chromium": { + "version": "1.4.816", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.816.tgz", + "integrity": "sha512-EKH5X5oqC6hLmiS7/vYtZHZFTNdhsYG5NVPRN6Yn0kQHNBlT59+xSM8HBy66P5fxWpKgZbPqb+diC64ng295Jw==", + "dev": true + }, + "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/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/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/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "dev": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "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, + "peer": 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, + "peer": 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/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@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-yscope": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/eslint-config-yscope/-/eslint-config-yscope-0.0.31.tgz", + "integrity": "sha512-cA6sS3G4Ydoht/CvST7C7moqJO+NiKl0InQtkX5YKocXAQE6KZ/VW9/kORdNnpIGCLwkqvMOa7y4XNJZnTfubw==", + "dev": true, + "peerDependencies": { + "@stylistic/eslint-plugin-js": "^1.6.2", + "@stylistic/eslint-plugin-jsx": "^1.6.2", + "@stylistic/eslint-plugin-plus": "^1.6.2", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import-newlines": "^1.4.0", + "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-no-autofix": "^1.2.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-simple-import-sort": "^12.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, + "peer": 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, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "peer": 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, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "peer": 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.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import-newlines": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.4.0.tgz", + "integrity": "sha512-+Cz1x2xBLtI9gJbmuYEpvY7F8K75wskBmJ7rk4VRObIJo+jklUJaejFJgtnWeL0dCFWabGEkhausrikXaNbtoQ==", + "dev": true, + "peer": true, + "bin": { + "import-linter": "lib/index.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-import/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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, + "peer": 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, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", + "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.43.1", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "parse-imports": "^2.1.0", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-plugin-no-autofix": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-autofix/-/eslint-plugin-no-autofix-1.2.3.tgz", + "integrity": "sha512-JFSYe82Da2A8Krh+Gfq7+3X2pchTScKgmrlMKIA4HmV6t5xGBF/kgjiFL3YTWRQXQ0NB9eOqpcxh6SuLtQUFjQ==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0", + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "eslint": ">= 5.12.1" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", + "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.hasown": "^1.1.4", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "peer": 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-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, + "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, + "peer": 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-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, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "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, + "peer": 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/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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, + "peer": 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, + "peer": 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, + "peer": true + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "peer": 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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "peer": 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/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "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/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": "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/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.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/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/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "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-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "peer": 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-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "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, + "peer": true + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "peer": true, + "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/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, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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/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-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "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, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "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, + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "peer": true + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "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, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "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/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==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": 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==", + "dev": true, + "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==", + "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, + "peer": 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, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "peer": 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/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, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "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, + "peer": 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/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, + "peer": true + }, + "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/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, + "peer": 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.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "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.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "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-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-minifier-terser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", + "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "clean-css": "^5.2.2", + "commander": "^8.3.0", + "he": "^1.2.0", + "param-case": "^3.0.4", + "relateurl": "^0.2.7", + "terser": "^5.10.0" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-webpack-plugin": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^6.0.0", + "html-minifier-terser": "^6.0.2", + "lodash": "^4.17.21", + "pretty-error": "^4.0.0", + "tapable": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/html-webpack-plugin" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.20.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "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-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-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/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/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "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/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "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==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/import-local/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/import-local/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/import-local/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/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, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "peer": 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/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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==" + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": 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==", + "dev": true, + "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, + "peer": 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, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "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-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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/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/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, + "peer": 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-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": 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, + "peer": 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.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "peer": true + }, + "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/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.0.tgz", + "integrity": "sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==", + "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/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/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/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": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "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, + "peer": 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==" + }, + "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/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, + "peer": 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==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "peer": 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/launch-editor": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.8.0.tgz", + "integrity": "sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "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, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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==" + }, + "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": "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/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, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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.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.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, + "peer": true + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "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/memfs": { + "version": "4.9.3", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.3.tgz", + "integrity": "sha512-bsYSSnirtYTWi1+OPMFb0M048evMKyUYe0EbtuGQgq6BVQM1g1W8/KIUJCCvjgI/El0j6Q4WsmMiBwLUBSw8LA==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.1.2", + "tree-dump": "^1.0.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "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-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==", + "dev": true, + "peer": true, + "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.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "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": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/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, + "peer": true + }, + "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/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "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-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "peer": 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.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.hasown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", + "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "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.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/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, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "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/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", + "dev": true, + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "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==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "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, + "peer": 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==" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", + "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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.1.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.1.1.tgz", + "integrity": "sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", + "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "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.7", + "picocolors": "^1.0.1", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "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.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "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.1.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.0.tgz", + "integrity": "sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==", + "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, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-error": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", + "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^3.0.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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "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/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/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "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/raw-body/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/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "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/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "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/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "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/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/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/renderkid": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", + "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^6.0.1" + } + }, + "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.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "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/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "peer": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "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.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "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/schema-utils/node_modules/ajv": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/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/schema-utils/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/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": "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/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/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/debug/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/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/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "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.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/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "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/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "peer": true + }, + "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/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/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "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/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "peer": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true, + "peer": 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/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true + }, + "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/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-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/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-width-cjs/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==", + "dev": true + }, + "node_modules/string-width/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/string-width/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/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "dev": true, + "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": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/synckit": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", + "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "dev": true, + "peer": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "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/terser": { + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", + "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.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "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/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/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/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, + "peer": true + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "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/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==", + "dev": true, + "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/tree-dump": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz", + "integrity": "sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/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, + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "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, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "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.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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, + "peer": 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/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/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", + "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/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/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "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/webpack": { + "version": "5.92.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.92.1.tgz", + "integrity": "sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-attributes": "^1.9.5", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.17.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.11", + "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.10", + "watchpack": "^2.4.1", + "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-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", + "integrity": "sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "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.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/glob": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.2.tgz", + "integrity": "sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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-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/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/webpack/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/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/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, + "peer": 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-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "peer": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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-cjs/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==", + "dev": true + }, + "node_modules/wrap-ansi-cjs/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==", + "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/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/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, + "peer": true + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/components/log-viewer-webui/client/package.json b/components/log-viewer-webui/client/package.json new file mode 100644 index 000000000..8978b8aee --- /dev/null +++ b/components/log-viewer-webui/client/package.json @@ -0,0 +1,45 @@ +{ + "name": "log-viewer-webui-client", + "version": "0.1.0", + "description": "", + "main": "src/index.jsx", + "scripts": { + "build": "webpack --define-process-env-node-env production", + "lint:check": "npx eslint --no-eslintrc --config package.json src webpack.config.js", + "lint:fix": "npx eslint --fix --no-eslintrc --config package.json src webpack.config.js", + "start": "webpack serve" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "type": "module", + "devDependencies": { + "@babel/core": "^7.24.7", + "@babel/preset-env": "^7.24.7", + "@babel/preset-react": "^7.24.7", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.15", + "babel-loader": "^9.1.3", + "css-loader": "^7.1.2", + "eslint-config-yscope": "latest", + "html-webpack-plugin": "^5.6.0", + "mini-css-extract-plugin": "^2.9.0", + "react-refresh": "^0.14.2", + "style-loader": "^4.0.0", + "webpack": "^5.92.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^5.0.4" + }, + "eslintConfig": { + "extends": [ + "yscope/react" + ] + }, + "dependencies": { + "@emotion/react": "^11.13.0", + "@emotion/styled": "^11.13.0", + "@mui/joy": "^5.0.0-beta.48", + "@types/react": "^18.3.3", + "axios": "^1.7.2", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } +} diff --git a/components/log-viewer-webui/client/public/index.html b/components/log-viewer-webui/client/public/index.html new file mode 100644 index 000000000..c2e5d6b78 --- /dev/null +++ b/components/log-viewer-webui/client/public/index.html @@ -0,0 +1,17 @@ + + + + Log Viewer + + + + + + + + +
+ + diff --git a/components/log-viewer-webui/client/src/App.jsx b/components/log-viewer-webui/client/src/App.jsx new file mode 100644 index 000000000..6d7a65e45 --- /dev/null +++ b/components/log-viewer-webui/client/src/App.jsx @@ -0,0 +1,20 @@ +import {CssVarsProvider} from "@mui/joy/styles/CssVarsProvider"; + +import LOCAL_STORAGE_KEY from "./typings/LOCAL_STORAGE_KEY.js"; +import QueryStatus from "./ui/QueryStatus.jsx"; + + +/** + * Renders the main application. + * + * @return {JSX.Element} + */ +const App = () => { + return ( + + + + ); +}; + +export default App; diff --git a/components/log-viewer-webui/client/src/api/query.js b/components/log-viewer-webui/client/src/api/query.js new file mode 100644 index 000000000..be17d0fc8 --- /dev/null +++ b/components/log-viewer-webui/client/src/api/query.js @@ -0,0 +1,32 @@ +import axios from "axios"; + + +/** + * @typedef {object} ExtractIrResp + * @property {number} begin_msg_ix + * @property {number} end_msg_ix + * @property {string} file_split_id + * @property {boolean} is_last_ir_chunk + * @property {string} orig_file_id + * @property {string} path + * @property {string} _id + */ + +/** + * Submits a job to extract the split of an original file that contains a given log event. The file + * is extracted as a CLP IR file. + * + * @param {number|string} origFileId The ID of the original file + * @param {number} logEventIdx The index of the log event + * @param {Function} onUploadProgress Callback to handle upload progress events. + * @return {Promise>} + */ +const submitExtractIrJob = async (origFileId, logEventIdx, onUploadProgress) => { + return await axios.post( + "/query/extract-ir", + {logEventIdx, origFileId}, + {onUploadProgress} + ); +}; + +export {submitExtractIrJob}; diff --git a/components/log-viewer-webui/client/src/index.css b/components/log-viewer-webui/client/src/index.css new file mode 100644 index 000000000..2e657d8e6 --- /dev/null +++ b/components/log-viewer-webui/client/src/index.css @@ -0,0 +1,15 @@ +html { + height: 100%; + width: 100%; +} + +body { + margin: 0; + height: 100%; + width: 100%; +} + +#root { + height: 100%; + width: 100%; +} diff --git a/components/log-viewer-webui/client/src/index.jsx b/components/log-viewer-webui/client/src/index.jsx new file mode 100644 index 000000000..5cb6dbd4e --- /dev/null +++ b/components/log-viewer-webui/client/src/index.jsx @@ -0,0 +1,14 @@ +import {StrictMode} from "react"; +import {createRoot} from "react-dom/client"; + +import App from "./App"; + +import "./index.css"; + + +const root = createRoot(document.getElementById("root")); +root.render( + + + +); diff --git a/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js b/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js new file mode 100644 index 000000000..c1dc19979 --- /dev/null +++ b/components/log-viewer-webui/client/src/typings/LOCAL_STORAGE_KEY.js @@ -0,0 +1,8 @@ +/** + * Enum of `window.localStorage` keys. + */ +const LOCAL_STORAGE_KEY = Object.freeze({ + UI_THEME: "uiTheme", +}); + +export default LOCAL_STORAGE_KEY; diff --git a/components/log-viewer-webui/client/src/typings/query.js b/components/log-viewer-webui/client/src/typings/query.js new file mode 100644 index 000000000..b91a814f1 --- /dev/null +++ b/components/log-viewer-webui/client/src/typings/query.js @@ -0,0 +1,37 @@ +/** + * @typedef {number} QueryLoadingState + */ +let enumQueryLoadingState; +/** + * Enum of query loading state. + * + * @enum {QueryLoadingState} + */ +const QUERY_LOADING_STATES = Object.freeze({ + SUBMITTING: (enumQueryLoadingState = 0), + WAITING: ++enumQueryLoadingState, + LOADING: ++enumQueryLoadingState, +}); + +/** + * Descriptions for query loading states. + */ +const QUERY_LOADING_STATE_DESCRIPTIONS = Object.freeze({ + [QUERY_LOADING_STATES.SUBMITTING]: { + label: "Submitting query Job", + description: "Parsing arguments and submitting job to the server.", + }, + [QUERY_LOADING_STATES.WAITING]: { + label: "Waiting for job to finish", + description: "The job is running. Waiting for the job to finish.", + }, + [QUERY_LOADING_STATES.LOADING]: { + label: "Loading Log Viewer", + description: "The query has been completed and the results are being loaded.", + }, +}); + +export { + QUERY_LOADING_STATE_DESCRIPTIONS, + QUERY_LOADING_STATES, +}; diff --git a/components/log-viewer-webui/client/src/ui/Loading.css b/components/log-viewer-webui/client/src/ui/Loading.css new file mode 100644 index 000000000..d8bec1842 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/Loading.css @@ -0,0 +1,24 @@ +.loading-sheet { + height: 100%; + + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; +} + +.loading-progress-container { + width: 100%; +} + +.loading-stepper-container { + display: flex; + flex-grow: 1; + + align-items: center; +} + +.loading-stepper { + --Stepper-verticalGap: 2rem !important; +} diff --git a/components/log-viewer-webui/client/src/ui/Loading.jsx b/components/log-viewer-webui/client/src/ui/Loading.jsx new file mode 100644 index 000000000..e157d1224 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/Loading.jsx @@ -0,0 +1,130 @@ +import { + Box, + LinearProgress, + Sheet, + Step, + StepIndicator, + Stepper, + Typography, +} from "@mui/joy"; + +import { + QUERY_LOADING_STATE_DESCRIPTIONS, + QUERY_LOADING_STATES, +} from "../typings/query.js"; + +import "./Loading.css"; + + +/** + * Renders a step with a label and description. + * + * @param {object} props + * @param {string} props.description + * @param {boolean} props.isActive + * @param {boolean} props.isError + * @param {string} props.label + * @param {number | string} props.stepIndicatorText + * @return {React.ReactElement} + */ +const LoadingStep = ({ + description, + isActive, + isError, + label, + stepIndicatorText, +}) => { + let color = isActive ? + "primary" : + "neutral"; + + if (isError) { + color = "danger"; + } + + return ( + + {stepIndicatorText} + + } + > + + {label} + + + {description} + + + ); +}; + +/** + * Displays status of a pending query job. + * + * @param {object} props + * @param {QueryLoadState} props.currentState + * @param {string} props.errorMsg + * @return {React.ReactElement} + */ +const Loading = ({currentState, errorMsg}) => { + const steps = []; + Object.values(QUERY_LOADING_STATES).forEach((state) => { + const isActive = (currentState === state); + const stateDescription = QUERY_LOADING_STATE_DESCRIPTIONS[state]; + steps.push( + + ); + if (isActive && null !== errorMsg) { + steps.push( + + ); + } + }); + + return ( + <> + + + + + + + {steps} + + + + + ); +}; + +export default Loading; diff --git a/components/log-viewer-webui/client/src/ui/QueryStatus.jsx b/components/log-viewer-webui/client/src/ui/QueryStatus.jsx new file mode 100644 index 000000000..d46e6b885 --- /dev/null +++ b/components/log-viewer-webui/client/src/ui/QueryStatus.jsx @@ -0,0 +1,80 @@ +import { + useEffect, + useRef, + useState, +} from "react"; + +import {AxiosError} from "axios"; + +import {submitExtractIrJob} from "../api/query.js"; +import {QUERY_LOADING_STATES} from "../typings/query.js"; +import Loading from "./Loading.jsx"; + + +/** + * Submits queries and renders the query states. + * + * @return {React.ReactElement} + */ +const QueryStatus = () => { + const [queryState, setQueryState] = useState(QUERY_LOADING_STATES.SUBMITTING); + const [errorMsg, setErrorMsg] = useState(null); + const isFirstRun = useRef(true); + + useEffect(() => { + if (false === isFirstRun.current) { + return; + } + isFirstRun.current = false; + + const searchParams = new URLSearchParams(window.location.search); + const origFileId = searchParams.get("origFileId"); + const logEventIdx = searchParams.get("logEventIdx"); + if (null === origFileId || null === logEventIdx) { + const error = "Either `origFileId` or `logEventIdx` are missing from the URL " + + "parameters. Note that non-IR-extraction queries are not supported at the moment."; + + console.error(error); + setErrorMsg(error); + return; + } + + submitExtractIrJob( + origFileId, + Number(logEventIdx), + () => { + setQueryState(QUERY_LOADING_STATES.WAITING); + } + ) + .then(({data}) => { + setQueryState(QUERY_LOADING_STATES.LOADING); + + const innerLogEventNum = logEventIdx - data.begin_msg_ix + 1; + window.location = `/log-viewer/index.html?filePath=/ir/${data.path}` + + `#logEventIdx=${innerLogEventNum}`; + }) + .catch((e) => { + let msg = "Unknown error."; + if (e instanceof AxiosError) { + msg = e.message; + if ("undefined" !== typeof e.response) { + if ("undefined" !== typeof e.response.data.message) { + msg = e.response.data.message; + } else { + msg = e.response.statusText; + } + } + } + console.error(msg, e); + setErrorMsg(msg); + }); + }, []); + + return ( + + ); +}; + +export default QueryStatus; diff --git a/components/log-viewer-webui/client/webpack.config.js b/components/log-viewer-webui/client/webpack.config.js new file mode 100644 index 000000000..61ec64bb6 --- /dev/null +++ b/components/log-viewer-webui/client/webpack.config.js @@ -0,0 +1,95 @@ +import HtmlWebpackPlugin from "html-webpack-plugin"; +import MiniCssExtractPlugin from "mini-css-extract-plugin"; +import * as path from "node:path"; +import {fileURLToPath} from "node:url"; + +import ReactRefreshPlugin from "@pmmmwh/react-refresh-webpack-plugin"; + + +const filename = fileURLToPath(import.meta.url); +const dirname = path.dirname(filename); + +const isProduction = "production" === process.env.NODE_ENV; + +const stylesHandler = isProduction ? + MiniCssExtractPlugin.loader : + "style-loader"; + +const plugins = [ + new HtmlWebpackPlugin({ + template: path.resolve(dirname, "public", "index.html"), + }), +]; + +const config = { + devServer: { + proxy: [ + { + context: ["/"], + target: "http://localhost:3000", + }, + ], + }, + devtool: isProduction ? + "source-map" : + "eval-source-map", + entry: path.resolve(dirname, "src", "index.jsx"), + module: { + rules: [ + { + test: /\.(js|jsx)$/i, + exclude: /node_modules/, + use: { + loader: "babel-loader", + options: { + presets: [ + "@babel/preset-env", + [ + "@babel/preset-react", + { + runtime: "automatic", + }, + ], + ], + plugins: isProduction ? + [] : + ["react-refresh/babel"], + }, + }, + }, + { + test: /\.css$/i, + use: [ + stylesHandler, + "css-loader", + ], + }, + ], + }, + output: { + path: path.resolve(dirname, "dist"), + filename: isProduction ? + "[name].[contenthash].bundle.js" : + "[name].bundle.js", + clean: true, + publicPath: "auto", + }, + plugins: plugins.concat(isProduction ? + [new MiniCssExtractPlugin()] : + [new ReactRefreshPlugin()]), + resolve: { + extensions: [ + ".js", + ".jsx", + ".json", + ], + }, +}; + +export default () => { + config.mode = isProduction ? + "production" : + "development"; + + return config; +}; diff --git a/components/log-viewer-webui/package-lock.json b/components/log-viewer-webui/package-lock.json new file mode 100644 index 000000000..4ca32e8ff --- /dev/null +++ b/components/log-viewer-webui/package-lock.json @@ -0,0 +1,351 @@ +{ + "name": "log-viewer-webui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui", + "version": "0.1.0", + "license": "Apache-2.0", + "devDependencies": { + "concurrently": "^8.2.2" + } + }, + "node_modules/@babel/runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/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/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/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": { + "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/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/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==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "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/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==", + "dev": true, + "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/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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/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/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/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/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/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/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/components/log-viewer-webui/package.json b/components/log-viewer-webui/package.json new file mode 100644 index 000000000..d019ba21b --- /dev/null +++ b/components/log-viewer-webui/package.json @@ -0,0 +1,22 @@ +{ + "name": "log-viewer-webui", + "version": "0.1.0", + "description": "", + "scripts": { + "client:lint:check": "cd client && npm run lint:check", + "client:lint:fix": "cd client && npm run lint:fix", + "client:start": "cd client && npm start", + "init": "npm i && (cd client && npm i) && (cd server && npm i)", + "lint:check": "npm run client:lint:check && npm run server:lint:check", + "lint:fix": "npm run client:lint:fix && npm run server:lint:fix", + "server:lint:check": "cd server && npm run lint:check", + "server:lint:fix": "cd server && npm run lint:fix", + "server:start": "cd server && npm start", + "start": "concurrently \"npm run client:start\" \"npm run server:start\"" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "devDependencies": { + "concurrently": "^8.2.2" + } +} diff --git a/components/log-viewer-webui/server/.env b/components/log-viewer-webui/server/.env new file mode 100644 index 000000000..b66dc997b --- /dev/null +++ b/components/log-viewer-webui/server/.env @@ -0,0 +1,8 @@ +CLIENT_DIR=../client/dist +IR_DATA_DIR=../../../build/clp-package/var/data/ir +LOG_VIEWER_DIR=../yscope-log-viewer/dist + +HOST=localhost +PORT=3000 +CLP_DB_USER=clp-user +CLP_DB_PASS= diff --git a/components/log-viewer-webui/server/.gitignore b/components/log-viewer-webui/server/.gitignore new file mode 100644 index 000000000..e19dcc6a4 --- /dev/null +++ b/components/log-viewer-webui/server/.gitignore @@ -0,0 +1,5 @@ + # Local development +.env.local + +# Testing +/.tap diff --git a/components/log-viewer-webui/server/package-lock.json b/components/log-viewer-webui/server/package-lock.json new file mode 100644 index 000000000..b5ad7d428 --- /dev/null +++ b/components/log-viewer-webui/server/package-lock.json @@ -0,0 +1,9624 @@ +{ + "name": "log-viewer-webui-server", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "log-viewer-webui-server", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "@fastify/mongodb": "^8.0.0", + "@fastify/mysql": "^4.3.0", + "@fastify/static": "^7.0.4", + "@msgpack/msgpack": "^3.0.0-beta2", + "dotenv": "^16.4.5", + "fastify": "^4.28.0", + "fastify-plugin": "^4.5.1", + "http-status-codes": "^2.3.0", + "pino-pretty": "^11.2.1" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.8", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "eslint-config-yscope": "latest", + "nodemon": "^3.1.3", + "tap": "^19.2.5" + } + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.1.3.tgz", + "integrity": "sha512-3yWxPTq3UQ/FY9p1ErPxIyfT64elWaMvM9lIHnaqpyft63tkxodF5aUElYHrdisWve5cETkh1+KBw1yJuW0aRw==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.13.1" + } + }, + "node_modules/@alcalzone/ansi-tokenize/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/@alcalzone/ansi-tokenize/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ampproject/remapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.9.tgz", + "integrity": "sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.9.tgz", + "integrity": "sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.9", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-module-transforms": "^7.24.9", + "@babel/helpers": "^7.24.8", + "@babel/parser": "^7.24.8", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.9", + "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/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/eslint-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.8.tgz", + "integrity": "sha512-nYAikI4XTGokU2QX7Jx+v4rxZKhKivaQaREZjuW3mrJrbdWJ5yUfohnoUULge+zEEaKjPYNxhoRgUKktjXtbwA==", + "dev": true, + "dependencies": { + "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", + "eslint-visitor-keys": "^2.1.0", + "semver": "^6.3.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || >=14.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0", + "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/eslint-parser/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/generator": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.9.tgz", + "integrity": "sha512-G8v3jRg+z8IwY1jHFxvCNhOPYPterE4XljNgdGTYfSTtzzwjIswIzIaSPSLs3R7yFuqnqNeay5rjICfqVr+/6A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.9", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz", + "integrity": "sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz", + "integrity": "sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.8.tgz", + "integrity": "sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/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, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/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, + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/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, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz", + "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==", + "dev": true, + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.7.tgz", + "integrity": "sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.8.tgz", + "integrity": "sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.24.8", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", + "@babel/parser": "^7.24.8", + "@babel/types": "^7.24.8", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz", + "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@base2/pretty-print-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz", + "integrity": "sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==", + "dev": true + }, + "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/@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/@es-joy/jsdoccomment": { + "version": "0.46.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz", + "integrity": "sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ==", + "dev": true, + "peer": true, + "dependencies": { + "comment-parser": "1.4.1", + "esquery": "^1.6.0", + "jsdoc-type-pratt-parser": "~4.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "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, + "peer": 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.11.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", + "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, + "peer": 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, + "peer": 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/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, + "node_modules/@fastify/ajv-compiler/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==" + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@fastify/mongodb": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@fastify/mongodb/-/mongodb-8.0.0.tgz", + "integrity": "sha512-IDw/wWpdc53+Y5sPpMg+ek71HOIVuz8NoD2GlfIOcvGE/lYdrZvnFQxqJcaZtlwPZ7YflDDkIu5aNkCPWdZQ0Q==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mongodb": "^6.0.0" + } + }, + "node_modules/@fastify/mysql": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/mysql/-/mysql-4.3.0.tgz", + "integrity": "sha512-eVx5/PyMmoBWp3hTaqdvXiZdo8YnKsAx3k/8AEXgI/MjUbgcn8YrSdy8eHSpCL3YZtBhD/2vLpOXFFciyqlWjQ==", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mysql2": "^3.9.7" + } + }, + "node_modules/@fastify/send": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", + "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "dependencies": { + "@lukeed/ms": "^2.0.1", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "2.0.0", + "mime": "^3.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "fastq": "^1.17.0", + "glob": "^10.3.4" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "peer": 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==", + "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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ts-node-temp-fork-for-pr-2009": { + "version": "10.9.7", + "resolved": "https://registry.npmjs.org/@isaacs/ts-node-temp-fork-for-pr-2009/-/ts-node-temp-fork-for-pr-2009-10.9.7.tgz", + "integrity": "sha512-9f0bhUr9TnwwpgUhEpr3FjxSaH/OHaARkE2F9fM0lS4nIs2GNerrvGwQz493dk0JKlTaGYVrKbq36vA/whZ34g==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node14": "*", + "@tsconfig/node16": "*", + "@tsconfig/node18": "*", + "@tsconfig/node20": "*", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.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" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=4.2" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/@isaacs/ts-node-temp-fork-for-pr-2009/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/@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/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "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/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", + "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.0.0-beta2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.0.0-beta2.tgz", + "integrity": "sha512-y+l1PNV0XDyY8sM3YtuMLK5vE3/hkfId+Do8pLo/OPxfxuFAUwcGz3oiiUuV46/aBpwTzZ+mRWVMtlSKbradhw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { + "version": "5.1.1-v1", + "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", + "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", + "dev": true, + "dependencies": { + "eslint-scope": "5.1.1" + } + }, + "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/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/@nicolo-ribaudo/eslint-scope-5-internals/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/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "peer": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "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.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", + "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.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/git/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/git/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/@npmcli/git/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/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "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.2.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.0.tgz", + "integrity": "sha512-qe/kiqqkW0AGtvBjL8TJKZk/eBBSpnJkUWvHdQ9jM2lKHXRYYJuyNpJPlJw3c8QjC2ow6NZYiLExhUaeJelbxQ==", + "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": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/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/promise-spawn/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/redact": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-1.1.0.tgz", + "integrity": "sha512-PfnWuOkQgu7gCbnSsAisaX7hKOdZ4wSAhAzH3/ph5dSGau52kCRrMMGbiSQLwyTZpgldkZ49b0brkOr1AzGBHQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-7.0.4.tgz", + "integrity": "sha512-9ApYM/3+rBt9V80aYg6tZfzj3UWdiYyCt7gJUD1VJKvWF5nwKDSICXbYIQbspFTq6TOpbsEtIC0LArB8d9PFmg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/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/run-script/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/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.8.1.tgz", + "integrity": "sha512-c5c2C8Mos5tTQd+NWpqwEu7VT6SSRooAguFPMj1cp2RkTYl1ynKoXo8MWy3k4rkbzoeYHrqC2UlUzsroAN7wtQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-jsx": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-jsx/-/eslint-plugin-jsx-1.8.1.tgz", + "integrity": "sha512-k1Eb6rcjMP+mmjvj+vd9y5KUdWn1OBkkPLHXhsrHt5lCDFZxJEs0aVQzE5lpYrtVZVkpc5esTtss/cPJux0lfA==", + "dev": true, + "peer": true, + "dependencies": { + "@stylistic/eslint-plugin-js": "^1.8.1", + "@types/eslint": "^8.56.10", + "estraverse": "^5.3.0", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" + } + }, + "node_modules/@stylistic/eslint-plugin-plus": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-plus/-/eslint-plugin-plus-1.8.1.tgz", + "integrity": "sha512-4+40H3lHYTN8OWz+US8CamVkO+2hxNLp9+CAjorI7top/lHqemhpJvKA1LD9Uh+WMY9DYWiWpL2+SZ2wAXY9fQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/eslint": "^8.56.10", + "@typescript-eslint/utils": "^6.21.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@tapjs/after": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/@tapjs/after/-/after-1.1.31.tgz", + "integrity": "sha512-531NkYOls9PvqfnLsEDRzIWwjynoFRbUVq7pTYuA3PRIw4Ka7jA9uUjILeUurcWjaHrQNzUua0jj/Yu94f6YYw==", + "dev": true, + "dependencies": { + "is-actual-promise": "^1.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/after-each": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/after-each/-/after-each-2.0.8.tgz", + "integrity": "sha512-btkpQ/BhmRyG50rezduxEZb3pMJblECvTQa41+U2ln2te1prDTlllHlpq4lOjceUksl8KFF1avDqcBqIqPzneQ==", + "dev": true, + "dependencies": { + "function-loop": "^4.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/asserts": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/asserts/-/asserts-2.0.8.tgz", + "integrity": "sha512-57VrI0p2kAqfgHHUwowDvd31eTfDHw3HO4FSSVUCvngPGWa96R6eH9gXa9fNig4qIp4Dup+nI7gJlJfU0R80SA==", + "dev": true, + "dependencies": { + "@tapjs/stack": "2.0.1", + "is-actual-promise": "^1.0.1", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/before": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/before/-/before-2.0.8.tgz", + "integrity": "sha512-22ZdGSn/zOKf8J8cb3yfw5R4I/ozdHEDKL8lBWon/zsxxMMvaRTgOtFXEjb4RE+5SDrqQ4NM7ZRYPGhE7T97dw==", + "dev": true, + "dependencies": { + "is-actual-promise": "^1.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/before-each": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/before-each/-/before-each-2.0.8.tgz", + "integrity": "sha512-Xjgk8/fuP7iFa5CYjFDl05p5PZGRe//VyHJNuYNzWpF1K9PNMtVdlmwplfpFmbrNrw/bIPq7R6LuiPmTBgzuOw==", + "dev": true, + "dependencies": { + "function-loop": "^4.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/chdir": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@tapjs/chdir/-/chdir-1.1.4.tgz", + "integrity": "sha512-axXkT5kWp2/X8l6inKyrqzUhqgvsgrWI8/0xLAdmirpFZ8H6gFxrl763Ozdm27EAmkLnnnWgFITPqUQCuB/tMA==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/config": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/config/-/config-3.1.6.tgz", + "integrity": "sha512-5gkDMSLXL5798bbCdX4RdLpB4OUQeu9TXftzKmL1+1T2xbcd4q7zfDnCfOB9zTk50x2f04+4h6Q7Z1NcSKIspg==", + "dev": true, + "dependencies": { + "@tapjs/core": "2.1.6", + "@tapjs/test": "2.2.4", + "chalk": "^5.2.0", + "jackspeak": "^3.1.2", + "polite-json": "^4.0.1", + "tap-yaml": "2.2.2", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6", + "@tapjs/test": "2.2.4" + } + }, + "node_modules/@tapjs/config/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/@tapjs/core": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/core/-/core-2.1.6.tgz", + "integrity": "sha512-NYMp0bl52DxXfcLmivMKvOIE14aaB9qJjdHeUbs6GZ9yxgD5w0yeiOT+gWEL+1PzZgGWRxSFEpghID1YfXAc4w==", + "dev": true, + "dependencies": { + "@tapjs/processinfo": "^3.1.8", + "@tapjs/stack": "2.0.1", + "@tapjs/test": "2.2.4", + "async-hook-domain": "^4.0.1", + "diff": "^5.2.0", + "is-actual-promise": "^1.0.1", + "minipass": "^7.0.4", + "signal-exit": "4.1", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/@tapjs/error-serdes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/error-serdes/-/error-serdes-2.0.1.tgz", + "integrity": "sha512-P+M4rtcfkDsUveKKmoRNF+07xpbPnRY5KrstIUOnyn483clQ7BJhsnWr162yYNCsyOj4zEfZmAJI1f8Bi7h/ZA==", + "dev": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/filter": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/filter/-/filter-2.0.8.tgz", + "integrity": "sha512-/ps6nOS3CTh1WLfCjJnU7tS4PH4KFgEasFSVPCIFN+BasyoqDapzj4JKIlzQvppZOGTQadKH3wUakafZl7uz8w==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/fixture": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/fixture/-/fixture-2.0.8.tgz", + "integrity": "sha512-LJnjeAMSozPFXzu+wQw2HJsjA9djHbTcyeMnsgiRL/Q8ffcLqAawV3SN6XKdDLdWYUg3e1fXhHspnbsouZj+xA==", + "dev": true, + "dependencies": { + "mkdirp": "^3.0.0", + "rimraf": "^5.0.5" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/fixture/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/intercept": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/intercept/-/intercept-2.0.8.tgz", + "integrity": "sha512-OF2Q35jtZ20bwV4hRNoca7vqIrzPFR3JR25G2rGru+fgPmq4heN0RLoh0d1O34AbrtXqra2lXkacMB/DPgb01A==", + "dev": true, + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/stack": "2.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/mock": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@tapjs/mock/-/mock-2.1.6.tgz", + "integrity": "sha512-bNXKrjg/r+i/gfKij5Oo/5Md2DvGNHPSRCHQmjz3VQjpyxqK7S1FGcR0kyqJ8Nof6Wc8yIhpNOCuibj19200IQ==", + "dev": true, + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/stack": "2.0.1", + "resolve-import": "^1.4.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/node-serialize": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/node-serialize/-/node-serialize-2.0.8.tgz", + "integrity": "sha512-92oqhkmIz5wr0yRs1CPQfim5JSwHPSmoDWnQmJlYUZsY1OYgYouQm3ifnPkqK/9hJpVYzlZEQmefxehxbs2WNQ==", + "dev": true, + "dependencies": { + "@tapjs/error-serdes": "2.0.1", + "@tapjs/stack": "2.0.1", + "tap-parser": "16.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/processinfo": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@tapjs/processinfo/-/processinfo-3.1.8.tgz", + "integrity": "sha512-FIriEB+qqArPhmVYc1PZwRHD99myRdl7C9Oe/uts04Q2LOxQ5MEmqP9XOP8vVYzpDOYwmL8OmL6eOYt9eZlQKQ==", + "dev": true, + "dependencies": { + "pirates": "^4.0.5", + "process-on-spawn": "^1.0.0", + "signal-exit": "^4.0.2", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=16.17" + } + }, + "node_modules/@tapjs/reporter": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/reporter/-/reporter-2.0.8.tgz", + "integrity": "sha512-tZn5ZHIrFwjbi59djtdXHBwgSIZSBXdJpz2i9CZ9HEC1nFhWtIr2Jczvrz4ScfixUgA0GNFirz+q+9iA4IFMvw==", + "dev": true, + "dependencies": { + "@tapjs/config": "3.1.6", + "@tapjs/stack": "2.0.1", + "chalk": "^5.2.0", + "ink": "^4.4.1", + "minipass": "^7.0.4", + "ms": "^2.1.3", + "patch-console": "^2.0.0", + "prismjs-terminal": "^1.2.3", + "react": "^18.2.0", + "string-length": "^6.0.0", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/reporter/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/@tapjs/reporter/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/@tapjs/run": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@tapjs/run/-/run-2.1.7.tgz", + "integrity": "sha512-Hk41E68f1x4eLBm6Rrxx4ARzZzrjwaLbKThb16+f3bGYiajmqAvBdeyNEoQpEWmW+Sv2HSlueOk2SS2P4fyetg==", + "dev": true, + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/before": "2.0.8", + "@tapjs/config": "3.1.6", + "@tapjs/processinfo": "^3.1.8", + "@tapjs/reporter": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/test": "2.2.4", + "c8": "^9.1.0", + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "foreground-child": "^3.1.1", + "glob": "^10.3.16", + "minipass": "^7.0.4", + "mkdirp": "^3.0.1", + "opener": "^1.5.2", + "pacote": "^17.0.6", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.5", + "semver": "^7.6.0", + "signal-exit": "^4.1.0", + "tap-parser": "16.0.1", + "tap-yaml": "2.2.2", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0", + "which": "^4.0.0" + }, + "bin": { + "tap-run": "dist/esm/index.js" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/run/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/@tapjs/run/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/@tapjs/run/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/run/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/@tapjs/snapshot": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/snapshot/-/snapshot-2.0.8.tgz", + "integrity": "sha512-L0vtqWKkgnQt/XNQkvHOme9Np7ffteCNf1P0F9mz2YiJion4er1nv6pZuJoKVxXFQsbNd2k+LGyx0Iw+bIzwFg==", + "dev": true, + "dependencies": { + "is-actual-promise": "^1.0.1", + "tcompare": "7.0.1", + "trivial-deferred": "^2.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/spawn": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/spawn/-/spawn-2.0.8.tgz", + "integrity": "sha512-vCYwynIYJNijY87uHFANe+gCu9rdGoe4GOBmghl6kwDy7eISmcN/FW5TlmrjePMNhTvrDMeYqOIAzqh3WRYmPA==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/stack": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tapjs/stack/-/stack-2.0.1.tgz", + "integrity": "sha512-3rKbZkRkLeJl9ilV/6b80YfI4C4+OYf7iEz5/d0MIVhmVvxv0ttIy5JnZutAc4Gy9eRp5Ne5UTAIFOVY5k36cg==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/stdin": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/stdin/-/stdin-2.0.8.tgz", + "integrity": "sha512-tW/exLXuDqjtH2wjptiPHXBahkdSyoppxDY56l9MG4tiz66dMN6NTCZFvQxp7+3t+lsQKqJp/74z8T/ayp+vZA==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/test": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@tapjs/test/-/test-2.2.4.tgz", + "integrity": "sha512-QIgq2BhMpwO9SN8I0qlwZYXAllO4xWCfJ0MgAGhc+J7p69B5p9dDNPmyOreHeXWMmk6VlNj3oWveoXb5Zn9xZQ==", + "dev": true, + "dependencies": { + "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7", + "@tapjs/after": "1.1.31", + "@tapjs/after-each": "2.0.8", + "@tapjs/asserts": "2.0.8", + "@tapjs/before": "2.0.8", + "@tapjs/before-each": "2.0.8", + "@tapjs/chdir": "1.1.4", + "@tapjs/filter": "2.0.8", + "@tapjs/fixture": "2.0.8", + "@tapjs/intercept": "2.0.8", + "@tapjs/mock": "2.1.6", + "@tapjs/node-serialize": "2.0.8", + "@tapjs/snapshot": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/typescript": "1.4.13", + "@tapjs/worker": "2.0.8", + "glob": "^10.3.16", + "jackspeak": "^3.1.2", + "mkdirp": "^3.0.0", + "package-json-from-dist": "^1.0.0", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.5", + "sync-content": "^1.0.1", + "tap-parser": "16.0.1", + "tshy": "^1.14.0", + "typescript": "5.4", + "walk-up-path": "^3.0.1" + }, + "bin": { + "generate-tap-test-class": "dist/esm/build.mjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/test/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@tapjs/test/node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@tapjs/typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/@tapjs/typescript/-/typescript-1.4.13.tgz", + "integrity": "sha512-MNs7zlhM6G3pNUIjkKXDxgNCwCGZt2bUCGtVunSTDVIrKiUlHAl4QSjQ1oTjumHlCi9gFIWiwFAvpHekzFti0w==", + "dev": true, + "dependencies": { + "@isaacs/ts-node-temp-fork-for-pr-2009": "^10.9.7" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tapjs/worker": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@tapjs/worker/-/worker-2.0.8.tgz", + "integrity": "sha512-AySf2kV6OHvwgD3DrLdT2az2g4hRdoRtKsFCLdZo3jOoKte+ft/IQJEnOW7CPT0RYUskS3elv6eabYgSyTH4tg==", + "dev": true, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "peerDependencies": { + "@tapjs/core": "2.1.6" + } + }, + "node_modules/@tsconfig/node14": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-14.1.2.tgz", + "integrity": "sha512-1vncsbfCZ3TBLPxesRYz02Rn7SNJfbLoDVkcZ7F/ixOV6nwxwgdhD1mdPcc5YQ413qBJ8CvMxXMFfJ7oawjo7Q==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-16.1.3.tgz", + "integrity": "sha512-9nTOUBn+EMKO6rtSZJk+DcqsfgtlERGT9XPJ5PRj/HNENPCBY1yu/JEj5wT6GLtbCLBO2k46SeXDaY0pjMqypw==", + "dev": true + }, + "node_modules/@tsconfig/node18": { + "version": "18.2.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node18/-/node18-18.2.4.tgz", + "integrity": "sha512-5xxU8vVs9/FNcvm3gE07fPbn9tl6tqGGWA9tSlwsUEkBxtRnTsNmwrV8gasZ9F/EobaSv9+nu8AxUKccw77JpQ==", + "dev": true + }, + "node_modules/@tsconfig/node20": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", + "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", + "dev": true + }, + "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/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "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, + "peer": true + }, + "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/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, + "peer": 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, + "peer": true + }, + "node_modules/@types/node": { + "version": "20.14.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", + "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "dev": true, + "peer": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true, + "peer": true + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "dev": true, + "peer": 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.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "9.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": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "dev": true, + "peer": 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.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "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/visitor-keys": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/types": "6.21.0", + "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, + "peer": true + }, + "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/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==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", + "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/aggregate-error/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/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": 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/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, + "node_modules/ajv-formats/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==" + }, + "node_modules/ansi-escapes": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-6.2.1.tgz", + "integrity": "sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==", + "dev": true, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "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, + "peer": 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, + "peer": 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.toreversed": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", + "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-hook-domain": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/async-hook-domain/-/async-hook-domain-4.0.1.tgz", + "integrity": "sha512-bSktexGodAjfHWIrSrrqxqWzf1hWBZBpmPNZv+TYUMyWa2eoefFc6q6H1+KtdHYSz35lrhWdmXt/XK9wNEZvww==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/avvio": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.3.2.tgz", + "integrity": "sha512-st8e519GWHa/azv8S87mcJvZs4WsgTBjOw/Ih1CP6u+8SZvcOeAYNG6JbsIrAUUJJ7JfmrnOkR8ipDS+u9SIRQ==", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.1.tgz", + "integrity": "sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==", + "engines": { + "node": ">= 6.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==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.2.tgz", + "integrity": "sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001640", + "electron-to-chromium": "^1.4.820", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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/c8": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", + "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^3.1.1", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.1.6", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1" + }, + "bin": { + "c8": "bin/c8.js" + }, + "engines": { + "node": ">=14.14.0" + } + }, + "node_modules/cacache": { + "version": "18.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", + "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "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/cacache/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001642", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001642.tgz", + "integrity": "sha512-3XQ0DoRgLijXJErLSl+bLnJ+Et4KqV1PY6JJBGAFlsNsz31zeAIncyeZfLCabHK/jtSh+671RM9YMldxjUPZtA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "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, + "peer": 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/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/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/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-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "dev": true, + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/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/cli-truncate/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "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==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dev": true + }, + "node_modules/cliui/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==", + "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/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "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/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "dev": true, + "dependencies": { + "convert-to-spaces": "^2.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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/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/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==" + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 12.0.0" + } + }, + "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/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==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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, + "peer": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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, + "peer": 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/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, + "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "peer": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.828", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.828.tgz", + "integrity": "sha512-QOIJiWpQJDHAVO4P58pwb133Cwee0nbvy/MV1CwzZVGpkH1RX33N3vsaWRCpR6bF63AAq366neZrRTu7Qlsbbw==", + "dev": true, + "peer": true + }, + "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==" + }, + "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/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==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "peer": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.0.19.tgz", + "integrity": "sha512-zoMwbCcH5hwUkKJkT8kDIBZSz9I6mVG//+lDCinLCGov4+r7NIy0ld8o03M0cJxl2spVf6ESYVS6/gpIfq1FFw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.0.3", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "iterator.prototype": "^1.1.2", + "safe-array-concat": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", + "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "dev": true, + "peer": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "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, + "peer": 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, + "peer": 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/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "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==" + }, + "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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@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-yscope": { + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/eslint-config-yscope/-/eslint-config-yscope-0.0.31.tgz", + "integrity": "sha512-cA6sS3G4Ydoht/CvST7C7moqJO+NiKl0InQtkX5YKocXAQE6KZ/VW9/kORdNnpIGCLwkqvMOa7y4XNJZnTfubw==", + "dev": true, + "peerDependencies": { + "@stylistic/eslint-plugin-js": "^1.6.2", + "@stylistic/eslint-plugin-jsx": "^1.6.2", + "@stylistic/eslint-plugin-plus": "^1.6.2", + "eslint": "^8.57.0", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-import-newlines": "^1.4.0", + "eslint-plugin-jsdoc": "^48.2.3", + "eslint-plugin-no-autofix": "^1.2.3", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-simple-import-sort": "^12.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, + "peer": 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, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "peer": 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, + "peer": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "peer": 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.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import-newlines": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-newlines/-/eslint-plugin-import-newlines-1.4.0.tgz", + "integrity": "sha512-+Cz1x2xBLtI9gJbmuYEpvY7F8K75wskBmJ7rk4VRObIJo+jklUJaejFJgtnWeL0dCFWabGEkhausrikXaNbtoQ==", + "dev": true, + "peer": true, + "bin": { + "import-linter": "lib/index.js" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-plugin-import/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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, + "peer": 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, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/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, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "48.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.7.0.tgz", + "integrity": "sha512-5oiVf7Y+ZxGYQTlLq81X72n+S+hjvS/u0upAdbpPEeaIZILK3MKN8lm/6QqKioBjm/qZ0B5XpMQUtc2fUkqXAg==", + "dev": true, + "peer": true, + "dependencies": { + "@es-joy/jsdoccomment": "~0.46.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.5", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.6.0", + "parse-imports": "^2.1.1", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-no-autofix": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-no-autofix/-/eslint-plugin-no-autofix-1.2.3.tgz", + "integrity": "sha512-JFSYe82Da2A8Krh+Gfq7+3X2pchTScKgmrlMKIA4HmV6t5xGBF/kgjiFL3YTWRQXQ0NB9eOqpcxh6SuLtQUFjQ==", + "dev": true, + "peer": true, + "dependencies": { + "eslint-rule-composer": "^0.3.0", + "find-up": "^5.0.0" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "eslint": ">= 5.12.1" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.34.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.4.tgz", + "integrity": "sha512-Np+jo9bUwJNxCsT12pXtrGhJgT3T44T1sHhn1Ssr42XFn8TES0267wPGo5nNrMHi8qkyimDAX2BUmkf9pSaVzA==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.2", + "array.prototype.toreversed": "^1.1.2", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.0.19", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.8", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.0", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react/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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-react/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, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "peer": 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-plugin-react/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-12.1.1.tgz", + "integrity": "sha512-6nuzu4xwQtE3332Uz0to+TxDQYRLTKRESSc2hefVT48Zc8JthmN23Gx9lnYhu0FtkRSL1oxny3kJ2aveVhmOVA==", + "dev": true, + "peer": true, + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=4.0.0" + } + }, + "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, + "peer": 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-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, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": 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/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/events-to-array": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-2.0.3.tgz", + "integrity": "sha512-f/qE2gImHRa4Cp2y1stEOSgw8wTFyUdVJX7G//bMwbaV9JqISFxg99NbmVQeP7YLnDUZ2un851jlaDrlpmGehQ==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "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/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==" + }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==" + }, + "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==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "peer": 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-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, + "node_modules/fast-json-stringify/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==" + }, + "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, + "peer": true + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==" + }, + "node_modules/fastify": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.28.1.tgz", + "integrity": "sha512-kFWUtpNr4i7t5vY2EJPCN2KgMVpuqfU4NjnJNCgiNB900oiDeYqaNDRcAfeBbOF5hGixixxcKnOU4KN9z6QncQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==" + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-my-way": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.0.tgz", + "integrity": "sha512-HdWXgFYc6b1BJcOBDBwjqWuHJj1WYiqrxSh25qtU4DabpMFdj/gSunNBQb83t+8Zt67D7CXEzJWTkxaShMTMOA==", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "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/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, + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "peer": 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, + "peer": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "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/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.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==", + "dev": true, + "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, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function-loop": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/function-loop/-/function-loop-4.0.0.tgz", + "integrity": "sha512-f34iQBedYF3XcI93uewZZOnyscDragxgTK/eTvVB74k3fCD0ZorOi5BV9GS4M8rz/JoNi0Kl3qX5Y9MH3S/CLQ==", + "dev": true + }, + "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, + "peer": 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, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "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==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/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, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "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, + "peer": 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, + "peer": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "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, + "peer": 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, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "peer": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "peer": 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, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "peer": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "peer": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==" + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true + }, + "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/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-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.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==", + "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.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.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, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "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==" + }, + "node_modules/ini": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", + "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/ink": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/ink/-/ink-4.4.1.tgz", + "integrity": "sha512-rXckvqPBB0Krifk5rn/5LvQGmyXwCUpBfmTwbkQNBY9JY8RSl3b8OftBNEYxg4+SWUhEKcPifgope28uL9inlA==", + "dev": true, + "dependencies": { + "@alcalzone/ansi-tokenize": "^0.1.3", + "ansi-escapes": "^6.0.0", + "auto-bind": "^5.0.1", + "chalk": "^5.2.0", + "cli-boxes": "^3.0.0", + "cli-cursor": "^4.0.0", + "cli-truncate": "^3.1.0", + "code-excerpt": "^4.0.0", + "indent-string": "^5.0.0", + "is-ci": "^3.0.1", + "is-lower-case": "^2.0.2", + "is-upper-case": "^2.0.2", + "lodash": "^4.17.21", + "patch-console": "^2.0.0", + "react-reconciler": "^0.29.0", + "scheduler": "^0.23.0", + "signal-exit": "^3.0.7", + "slice-ansi": "^6.0.0", + "stack-utils": "^2.0.6", + "string-width": "^5.1.2", + "type-fest": "^0.12.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.1.0", + "ws": "^8.12.0", + "yoga-wasm-web": "~0.3.3" + }, + "engines": { + "node": ">=14.16" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0", + "react-devtools-core": "^4.19.1" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-devtools-core": { + "optional": true + } + } + }, + "node_modules/ink/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/ink/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/ink/node_modules/type-fest": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", + "integrity": "sha512-53RyidyjvkGpnWPMF9bQgFtWp+Sl8O2Rp13VavmJgfAP9WWG6q6TkrKU8iyJdnwnfgHI6k2hTlgqH4aSdjoTbg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "devOptional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-actual-promise": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-actual-promise/-/is-actual-promise-1.0.2.tgz", + "integrity": "sha512-xsFiO1of0CLsQnPZ1iXHNTyR9YszOeWKYv+q6n8oSFW3ipooFJ1j1lbRMgiMCr+pp2gLruESI4zb5Ak6eK5OnQ==", + "dev": true + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", + "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": 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==", + "dev": true, + "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, + "peer": 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, + "peer": 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.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dev": true, + "peer": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "peer": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.0.2.tgz", + "integrity": "sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dev": true, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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-lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-lower-case/-/is-lower-case-2.0.2.tgz", + "integrity": "sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "peer": 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==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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, + "peer": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "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, + "peer": 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-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": 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, + "peer": 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.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz", + "integrity": "sha512-44pxmxAvnnAOwBg4tHPnkfvgjPwbc5QIsSstNU+YcJ1ovxVzCWpSGosPJOZh/a1tdl81fbgnLc9LLv+x2ywbPQ==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", + "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "peer": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "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-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-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/iterator.prototype": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.2.tgz", + "integrity": "sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.2.1", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "reflect.getprototypeof": "^1.0.4", + "set-function-name": "^2.0.1" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "devOptional": true + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true, + "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, + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "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, + "peer": 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, + "peer": true + }, + "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, + "peer": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "peer": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/light-my-request": { + "version": "5.13.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.13.0.tgz", + "integrity": "sha512-9IjUN9ZyCS9pTG+KqTDEQo68Sui2lHsYBrfMyVUTTZ3XhH8PMZq7xO94Kr+eP9dhi/kcKsx4N41p2IXEBil1pQ==", + "dependencies": { + "cookie": "^0.6.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "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/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.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, + "peer": true + }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "engines": { + "node": ">=16.14" + } + }, + "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": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "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", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "peer": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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/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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "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/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-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-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-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/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/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "dev": true, + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mongodb": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", + "integrity": "sha512-HGQ9NWDle5WvwMnrvUxsFYPd3JEbqD3RgABHBQRuoCEND0qzhsd0iH5ypHsf1eJ+sXmvmyKpP+FLOKY8Il7jMw==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "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==", + "dev": true + }, + "node_modules/mysql2": { + "version": "3.10.3", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.10.3.tgz", + "integrity": "sha512-k43gmH9i79rZD4hGPdj7pDuT0UBiFjs4UzXEy1cJrV0QqcSABomoLwvejqdbcXN+Vd7gi999CVM6o9vCPKq29g==", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/named-placeholders/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==", + "engines": { + "node": ">=12" + } + }, + "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, + "peer": true + }, + "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/node-gyp": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", + "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "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": "^4.1.0", + "semver": "^7.3.5", + "tar": "^6.2.1", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/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/node-gyp/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/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true, + "peer": true + }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "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/normalize-package-data": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", + "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "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": "11.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", + "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", + "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "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/npm-registry-fetch": { + "version": "16.2.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-16.2.1.tgz", + "integrity": "sha512-8l+7jxhim55S85fjiDGJ1rZXBWGtRLi1OSb4Z3BPLObPuIaeKRlPRiYMSHU4/81ck3t71Z+UwDDl47gcpmfQQA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^1.1.0", + "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": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "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, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "peer": 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.entries": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.8.tgz", + "integrity": "sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "dependencies": { + "wrappy": "1" + } + }, + "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/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/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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": "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/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/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, + "node_modules/pacote": { + "version": "17.0.7", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.7.tgz", + "integrity": "sha512-sgvnoUMlkv9xHwDUKjKQFXVyUi8dtJGKp3vg6sYy+TxbDic5RjZCHF3ygv0EJgNRZ2GfRONjlKPUfokJ9lDpwQ==", + "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": "^4.0.0", + "promise-retry": "^2.0.1", + "read-package-json": "^7.0.0", + "read-package-json-fast": "^3.0.0", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "lib/bin.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "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, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.1.tgz", + "integrity": "sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==", + "dev": true, + "peer": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/patch-console": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/patch-console/-/patch-console-2.0.0.tgz", + "integrity": "sha512-0YNdUceMdaQwoKce1gatDScmMo5pu/tfABfnzEqeG0gtTmd7mh/WcwgUjtAeOU7N8nFFlbQBnFK2gXW5fGvmMA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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==", + "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, + "peer": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + }, + "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, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true, + "peer": true + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pino": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.3.1.tgz", + "integrity": "sha512-afSfrq/hUiW/MFmQcLEwV9Zh8Ry6MrMTOyBU53o/fc0gEl+1OZ/Fks/xQCM2nOC0C/OfDtQMnT2d8c3kpcfSzA==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.2.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^3.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.2.0.tgz", + "integrity": "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty": { + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-11.2.1.tgz", + "integrity": "sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^4.0.0", + "secure-json-parse": "^2.4.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^3.1.1" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==" + }, + "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/polite-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-4.0.1.tgz", + "integrity": "sha512-8LI5ZeCPBEb4uBbcYKNVwk4jgqNx1yHReWoW4H4uUihWlSqZsUDfSITrRhjliuPgxsNPFhNSudGO2Zu4cbWinQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "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, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", + "integrity": "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prismjs-terminal": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prismjs-terminal/-/prismjs-terminal-1.2.3.tgz", + "integrity": "sha512-xc0zuJ5FMqvW+DpiRkvxURlz98DdfDsZcFHdO699+oL+ykbFfgI7O4VDEgUyc07BSL2NHl3zdb8m/tZ/aaqUrw==", + "dev": true, + "dependencies": { + "chalk": "^5.2.0", + "prismjs": "^1.29.0", + "string-length": "^6.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/prismjs-terminal/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/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "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==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" + }, + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "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==", + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-element-to-jsx-string": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", + "integrity": "sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==", + "dev": true, + "dependencies": { + "@base2/pretty-print-object": "1.0.1", + "is-plain-object": "5.0.0", + "react-is": "18.1.0" + }, + "peerDependencies": { + "react": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0", + "react-dom": "^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0" + } + }, + "node_modules/react-element-to-jsx-string/node_modules/react-is": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz", + "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", + "dev": true + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "peer": true + }, + "node_modules/react-reconciler": { + "version": "0.29.2", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz", + "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/read-package-json": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-7.0.1.tgz", + "integrity": "sha512-8PcDiZ8DXUjLf687Ol4BR8Bpm2umR7vhoZOzNRt+uxD9GpBh/K+CAAALVIiYFknmvlmyg7hM7BSNUXPaCCqd0Q==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "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/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/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "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==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz", + "integrity": "sha512-fmfw4XgoDke3kdI6h4xcUz1dG8uaiv5q9gcEwLS4Pnth2kxT+GZ7YehS1JTMGBQmtV7Y4GFGbs2re2NqhdozUg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.1", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "globalthis": "^1.0.3", + "which-builtin-type": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "peer": 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/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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-import": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/resolve-import/-/resolve-import-1.4.6.tgz", + "integrity": "sha512-CIw9e64QcKcCFUj9+KxUCJPy8hYofv6eVfo3U9wdhCm2E4IjvFnZ6G4/yIC4yP3f11+h6uU5b3LdS7O64LgqrA==", + "dev": true, + "dependencies": { + "glob": "^10.3.3", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/restore-cursor/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/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "engines": { + "node": ">=10" + } + }, + "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.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "peer": 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/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, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true, + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "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==", + "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.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "dependencies": { + "ret": "~0.4.0" + } + }, + "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==", + "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==" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "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==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true, + "peer": true + }, + "node_modules/slice-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-6.0.0.tgz", + "integrity": "sha512-6bn4hRfkTvDfUoEQYkERg0BVF1D0vrX9HEkMl08uDiNWvVvjylLHvZFZWkDo6wjT8tUctbYl1nCOuE66ZTaUtA==", + "dev": true, + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/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/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "devOptional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "devOptional": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-boom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.0.1.tgz", + "integrity": "sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "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-correct/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-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "peer": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "devOptional": true + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-6.0.0.tgz", + "integrity": "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg==", + "dev": true, + "dependencies": { + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-length/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/string-length/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/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "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/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==", + "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/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/string-width/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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/string-width/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==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", + "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.7", + "regexp.prototype.flags": "^1.5.2", + "set-function-name": "^2.0.2", + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "peer": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sync-content": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/sync-content/-/sync-content-1.0.2.tgz", + "integrity": "sha512-znd3rYiiSxU3WteWyS9a6FXkTA/Wjk8WQsOyzHbineeL837dLn3DA4MRhsIX3qGcxDMH6+uuFV4axztssk7wEQ==", + "dev": true, + "dependencies": { + "glob": "^10.2.6", + "mkdirp": "^3.0.1", + "path-scurry": "^1.9.2", + "rimraf": "^5.0.1" + }, + "bin": { + "sync-content": "dist/mjs/bin.mjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sync-content/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "peer": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/tap": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/tap/-/tap-19.2.5.tgz", + "integrity": "sha512-Mz7MznUuKCqrN9dr0s8REt6zLg6WLNrvGXwDSaUyPO73dpXXjakYA7YVKRWu6TBnj7NsSYKuHXpQFROlqZ2KTg==", + "dev": true, + "dependencies": { + "@tapjs/after": "1.1.31", + "@tapjs/after-each": "2.0.8", + "@tapjs/asserts": "2.0.8", + "@tapjs/before": "2.0.8", + "@tapjs/before-each": "2.0.8", + "@tapjs/chdir": "1.1.4", + "@tapjs/core": "2.1.6", + "@tapjs/filter": "2.0.8", + "@tapjs/fixture": "2.0.8", + "@tapjs/intercept": "2.0.8", + "@tapjs/mock": "2.1.6", + "@tapjs/node-serialize": "2.0.8", + "@tapjs/run": "2.1.7", + "@tapjs/snapshot": "2.0.8", + "@tapjs/spawn": "2.0.8", + "@tapjs/stdin": "2.0.8", + "@tapjs/test": "2.2.4", + "@tapjs/typescript": "1.4.13", + "@tapjs/worker": "2.0.8", + "resolve-import": "^1.4.5" + }, + "bin": { + "tap": "dist/esm/run.mjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tap-parser": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/tap-parser/-/tap-parser-16.0.1.tgz", + "integrity": "sha512-vKianJzSSzLkJ3bHBwzvZDDRi9yGMwkRANJxwPAjAue50owB8rlluYySmTN4tZVH0nsh6stvrQbg9kuCL5svdg==", + "dev": true, + "dependencies": { + "events-to-array": "^2.0.3", + "tap-yaml": "2.2.2" + }, + "bin": { + "tap-parser": "bin/cmd.cjs" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/tap-yaml": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-2.2.2.tgz", + "integrity": "sha512-MWG4OpAKtNoNVjCz/BqlDJiwTM99tiHRhHPS4iGOe1ZS0CgM4jSFH92lthSFvvy4EdDjQZDV7uYqUFlU9JuNhw==", + "dev": true, + "dependencies": { + "yaml": "^2.4.1", + "yaml-types": "^0.3.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "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/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/tcompare": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/tcompare/-/tcompare-7.0.1.tgz", + "integrity": "sha512-JN5s7hgmg/Ya5HxZqCnywT+XiOGRFcJRgYhtMyt/1m+h0yWpWwApO7HIM8Bpwyno9hI151ljjp5eAPCHhIGbpQ==", + "dev": true, + "dependencies": { + "diff": "^5.2.0", + "react-element-to-jsx-string": "^15.0.0" + }, + "engines": { + "node": "16 >=16.17.0 || 18 >= 18.6.0 || >=20" + } + }, + "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/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/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==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "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/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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, + "peer": true + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "peer": true, + "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==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "engines": { + "node": ">=12" + } + }, + "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==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/trivial-deferred": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trivial-deferred/-/trivial-deferred-2.0.0.tgz", + "integrity": "sha512-iGbM7X2slv9ORDVj2y2FFUq3cP/ypbtu2nQ8S38ufjL0glBABvmR9pTdsib1XtS2LUhhLMbelaBUaf/s5J3dSw==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tshy": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/tshy/-/tshy-1.18.0.tgz", + "integrity": "sha512-FQudIujBazHRu7CVPHKQE9/Xq1Wc7lezxD/FCnTXx2PTcnoSN32DVpb/ZXvzV2NJBTDB3XKjqX8Cdm+2UK1DlQ==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "chokidar": "^3.6.0", + "foreground-child": "^3.1.1", + "minimatch": "^9.0.4", + "mkdirp": "^3.0.1", + "polite-json": "^5.0.0", + "resolve-import": "^1.4.5", + "rimraf": "^5.0.1", + "sync-content": "^1.0.2", + "typescript": "5", + "walk-up-path": "^3.0.1" + }, + "bin": { + "tshy": "dist/esm/index.js" + }, + "engines": { + "node": "16 >=16.17 || 18 >=18.15.0 || >=20.6.1" + } + }, + "node_modules/tshy/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/tshy/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tshy/node_modules/polite-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/polite-json/-/polite-json-5.0.0.tgz", + "integrity": "sha512-OLS/0XeUAcE8a2fdwemNja+udKgXNnY6yKVIXqAD2zVRx1KvY6Ato/rZ2vdzbxqYwPW0u6SCNC/bAMPNzpzxbw==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tshy/node_modules/rimraf": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.9.tgz", + "integrity": "sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "14 >=14.20 || 16 >=16.20 || >=18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "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, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "peer": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "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, + "peer": 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/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "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, + "peer": true + }, + "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/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.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.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "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/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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-license/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/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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/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==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "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, + "peer": 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-builtin-type": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", + "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "dev": true, + "peer": true, + "dependencies": { + "function.prototype.name": "^1.1.5", + "has-tostringtag": "^1.0.0", + "is-async-function": "^2.0.0", + "is-date-object": "^1.0.5", + "is-finalizationregistry": "^1.0.2", + "is-generator-function": "^1.0.10", + "is-regex": "^1.1.4", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "peer": true, + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "dev": true, + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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/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==", + "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/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/wrap-ansi-cjs/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/wrap-ansi/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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/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==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yaml-types": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/yaml-types/-/yaml-types-0.3.0.tgz", + "integrity": "sha512-i9RxAO/LZBiE0NJUy9pbN5jFz5EasYDImzRkj8Y81kkInTi1laia3P3K/wlMKzOxFQutZip8TejvQP/DwgbU7A==", + "dev": true, + "engines": { + "node": ">= 16", + "npm": ">= 7" + }, + "peerDependencies": { + "yaml": "^2.3.0" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "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==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/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==", + "dev": true + }, + "node_modules/yargs/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==", + "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/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/yoga-wasm-web": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz", + "integrity": "sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==", + "dev": true + } + } +} diff --git a/components/log-viewer-webui/server/package.json b/components/log-viewer-webui/server/package.json new file mode 100644 index 000000000..f952b6c86 --- /dev/null +++ b/components/log-viewer-webui/server/package.json @@ -0,0 +1,48 @@ +{ + "name": "log-viewer-webui-server", + "version": "0.1.0", + "description": "", + "main": "src/main.js", + "scripts": { + "lint:check": "npx eslint --no-eslintrc --config package.json src", + "lint:fix": "npx eslint --fix --no-eslintrc --config package.json src", + "prod": "NODE_ENV=production node src/main.js", + "start": "NODE_ENV=development nodemon src/main.js", + "test": "NODE_ENV=test tap" + }, + "author": "YScope Inc. ", + "license": "Apache-2.0", + "type": "module", + "dependencies": { + "@fastify/mongodb": "^8.0.0", + "@fastify/mysql": "^4.3.0", + "@fastify/static": "^7.0.4", + "fastify-plugin": "^4.5.1", + "@msgpack/msgpack": "^3.0.0-beta2", + "dotenv": "^16.4.5", + "fastify": "^4.28.0", + "http-status-codes": "^2.3.0", + "pino-pretty": "^11.2.1" + }, + "devDependencies": { + "@babel/eslint-parser": "^7.24.8", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "eslint-config-yscope": "latest", + "nodemon": "^3.1.3", + "tap": "^19.2.5" + }, + "eslintConfig": { + "extends": [ + "yscope/common" + ], + "parser": "@babel/eslint-parser", + "parserOptions": { + "requireConfigFile": false, + "babelOptions": { + "plugins": [ + "@babel/plugin-syntax-import-attributes" + ] + } + } + } +} diff --git a/components/log-viewer-webui/server/settings.json b/components/log-viewer-webui/server/settings.json new file mode 100644 index 000000000..bb1aac48a --- /dev/null +++ b/components/log-viewer-webui/server/settings.json @@ -0,0 +1,14 @@ +{ + "SqlDbHost": "localhost", + "SqlDbPort": 3306, + "SqlDbName": "clp-db", + "SqlDbQueryJobsTableName": "query_jobs", + "MongoDbHost": "localhost", + "MongoDbPort": 27017, + "MongoDbName": "clp-query-results", + "MongoDbIrFilesCollectionName": "ir-files", + + "ClientDir": "../client/dist", + "IrFilesDir": "../../../build/clp-package/var/data/ir", + "LogViewerDir": "../yscope-log-viewer/dist" +} diff --git a/components/log-viewer-webui/server/src/DbManager.js b/components/log-viewer-webui/server/src/DbManager.js new file mode 100644 index 000000000..2cb7ef3e0 --- /dev/null +++ b/components/log-viewer-webui/server/src/DbManager.js @@ -0,0 +1,241 @@ +import fastifyPlugin from "fastify-plugin"; + +import fastifyMongo from "@fastify/mongodb"; +import fastifyMysql from "@fastify/mysql"; +import msgpack from "@msgpack/msgpack"; + +import {sleep} from "./utils.js"; + + +/** + * Interval in milliseconds for polling the completion status of a job. + */ +const JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS = 0.5; + +/** + * Enum of the `query_jobs` table's column names. + * + * @enum {string} + */ +const QUERY_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ + ID: "id", + STATUS: "status", + TYPE: "type", + JOB_CONFIG: "job_config", +}); + +/* eslint-disable sort-keys */ +let enumQueryJobStatus; +/** + * Enum of job statuses, matching the `QueryJobStatus` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_STATUS = Object.freeze({ + PENDING: (enumQueryJobStatus = 0), + RUNNING: ++enumQueryJobStatus, + SUCCEEDED: ++enumQueryJobStatus, + FAILED: ++enumQueryJobStatus, + CANCELLING: ++enumQueryJobStatus, + CANCELLED: ++enumQueryJobStatus, +}); +/* eslint-enable sort-keys */ + +/** + * List of states that indicate the job is either pending or in progress. + */ +const QUERY_JOB_STATUS_WAITING_STATES = Object.freeze([ + QUERY_JOB_STATUS.PENDING, + QUERY_JOB_STATUS.RUNNING, + QUERY_JOB_STATUS.CANCELLING, +]); + +/* eslint-disable sort-keys */ +let enumQueryType; +/** + * Enum of job types, matching the `QueryJobType` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_TYPE = Object.freeze({ + SEARCH_OR_AGGREGATION: (enumQueryType = 0), + EXTRACT_IR: ++enumQueryType, +}); +/* eslint-enable sort-keys */ + +/** + * Class to manage connections to the jobs database (MySQL) and results cache (MongoDB). + */ +class DbManager { + /** + * @type {import("fastify").FastifyInstance | + * {mysql: import("@fastify/mysql").MySQLPromisePool} | + * {mongo: import("@fastify/mongodb").FastifyMongoObject}} + */ + #fastify; + + /** + * @type {import("@fastify/mysql").PromisePool} + */ + #mysqlConnectionPool; + + /** + * @type {import("mongodb").Collection} + */ + #irFilesCollection; + + #queryJobsTableName; + + /** + * @param {import("fastify").FastifyInstance} app + * @param {object} dbConfig + * @param {object} dbConfig.mysqlConfig + * @param {object} dbConfig.mongoConfig + */ + constructor (app, dbConfig) { + this.#fastify = app; + this.#initMySql(dbConfig.mysqlConfig); + this.#initMongo(dbConfig.mongoConfig); + } + + /** + * Submits an IR extraction job to the scheduler and waits for it to finish. + * + * @param {object} jobConfig + * @return {Promise} The ID of the job or null if an error occurred. + */ + async submitAndWaitForExtractIrJob (jobConfig) { + let jobId; + try { + const [result] = await this.#mysqlConnectionPool.query( + `INSERT INTO ${this.#queryJobsTableName} (job_config, type) + VALUES (?, ?)`, + [ + Buffer.from(msgpack.encode(jobConfig)), + QUERY_JOB_TYPE.EXTRACT_IR, + ] + ); + + ({insertId: jobId} = result); + await this.#awaitJobCompletion(jobId); + } catch (e) { + this.#fastify.log.error(e); + + return null; + } + + return jobId; + } + + /** + * Gets the metadata for an IR file extracted from part of an original file, where the original + * file has the given ID and the extracted part contains the given log event index. + * + * @param {string} origFileId + * @param {number} logEventIdx + * @return {Promise} A promise that resolves to the extracted IR file's metadata. + */ + async getExtractedIrFileMetadata (origFileId, logEventIdx) { + return await this.#irFilesCollection.findOne({ + orig_file_id: origFileId, + begin_msg_ix: {$lte: logEventIdx}, + end_msg_ix: {$gt: logEventIdx}, + }); + } + + /** + * Initializes the MySQL plugin. + * + * @param {object} config + * @param {string} config.user + * @param {string} config.password + * @param {string} config.host + * @param {number} config.port + * @param {string} config.database + * @param {string} config.queryJobsTableName + */ + #initMySql (config) { + this.#fastify.register(fastifyMysql, { + promise: true, + connectionString: `mysql://${config.user}:${config.password}@${config.host}:` + + `${config.port}/${config.database}`, + }).after(async (err) => { + if (err) { + throw err; + } + this.#mysqlConnectionPool = this.#fastify.mysql.pool; + this.#queryJobsTableName = config.queryJobsTableName; + }); + } + + /** + * Initializes the MongoDB plugin. + * + * @param {object} config + * @param {string} config.host + * @param {number} config.port + * @param {string} config.database + * @param {string} config.irFilesCollectionName + */ + #initMongo (config) { + this.#fastify.register(fastifyMongo, { + forceClose: true, + url: `mongodb://${config.host}:${config.port}/${config.database}`, + }).after((err) => { + if (err) { + throw err; + } + this.#irFilesCollection = + this.#fastify.mongo.db.collection(config.irFilesCollectionName); + }); + } + + /** + * Waits for the job with the given ID to finish. + * + * @param {number} jobId + * @throws {Error} If there's an error querying the job's status, the job is not found in the + * database, the job was cancelled, or it exited with an unexpected status. + */ + async #awaitJobCompletion (jobId) { + while (true) { + let rows; + try { + const [queryRows] = await this.#mysqlConnectionPool.query( + ` + SELECT ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + FROM ${this.#queryJobsTableName} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? + `, + jobId, + ); + + rows = queryRows; + } catch (e) { + throw new Error(`Failed to query status for job ${jobId} - ${e}`); + } + if (0 === rows.length) { + throw new Error(`Job ${jobId} not found in database.`); + } + const status = rows[0][QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS]; + + if (false === QUERY_JOB_STATUS_WAITING_STATES.includes(status)) { + if (QUERY_JOB_STATUS.CANCELLED === status) { + throw new Error(`Job ${jobId} was cancelled.`); + } else if (QUERY_JOB_STATUS.SUCCEEDED !== status) { + throw new Error(`Job ${jobId} exited with unexpected status=${status}: ` + + `${Object.keys(QUERY_JOB_STATUS)[status]}.`); + } + break; + } + + await sleep(JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS); + } + } +} + +export default fastifyPlugin(async (app, options) => { + await app.decorate("dbManager", new DbManager(app, options)); +}); diff --git a/components/log-viewer-webui/server/src/app.js b/components/log-viewer-webui/server/src/app.js new file mode 100644 index 000000000..69918ea58 --- /dev/null +++ b/components/log-viewer-webui/server/src/app.js @@ -0,0 +1,53 @@ +import fastify from "fastify"; +import process from "node:process"; + +import settings from "../settings.json" with {type: "json"}; +import DbManager from "./DbManager.js"; +import exampleRoutes from "./routes/example.js"; +import queryRoutes from "./routes/query.js"; +import staticRoutes from "./routes/static.js"; + + +/** + * Creates the Fastify app with the given options. + * + * @param {object} props + * @param {import("fastify").FastifyServerOptions} props.fastifyOptions + * @param {string} props.sqlDbUser + * @param {string} props.sqlDbPass + * @return {Promise} + */ +const app = async ({ + fastifyOptions, + sqlDbUser, + sqlDbPass, +}) => { + const server = fastify(fastifyOptions); + + if ("test" !== process.env.NODE_ENV) { + await server.register(DbManager, { + mysqlConfig: { + database: settings.SqlDbName, + host: settings.SqlDbHost, + password: sqlDbPass, + port: settings.SqlDbPort, + queryJobsTableName: settings.SqlDbQueryJobsTableName, + user: sqlDbUser, + }, + mongoConfig: { + database: settings.MongoDbName, + host: settings.MongoDbHost, + irFilesCollectionName: settings.MongoDbIrFilesCollectionName, + port: settings.MongoDbPort, + }, + }); + } + + await server.register(staticRoutes); + await server.register(exampleRoutes); + await server.register(queryRoutes); + + return server; +}; + +export default app; diff --git a/components/log-viewer-webui/server/src/app.test.js b/components/log-viewer-webui/server/src/app.test.js new file mode 100644 index 000000000..3a19208f7 --- /dev/null +++ b/components/log-viewer-webui/server/src/app.test.js @@ -0,0 +1,29 @@ +import httpStatusCodes from "http-status-codes"; +import {test} from "tap"; + +import app from "./app.js"; + + +test("Tests the example routes", async (t) => { + const server = await app({}); + t.teardown(() => server.close()); + + let resp = await server.inject({ + method: "GET", + url: "/example/get/Alice", + }); + + t.equal(resp.statusCode, httpStatusCodes.OK); + t.match(JSON.parse(resp.body), {msg: String}); + + resp = await server.inject({ + method: "POST", + url: "/example/post", + payload: {name: "Bob"}, + }); + t.equal(resp.statusCode, httpStatusCodes.OK); + t.match(JSON.parse(resp.body), {msg: String}); +}); + +// eslint-disable-next-line no-warning-comments +// TODO: Add tests for `query` routes. diff --git a/components/log-viewer-webui/server/src/main.js b/components/log-viewer-webui/server/src/main.js new file mode 100644 index 000000000..c3b7ef5cf --- /dev/null +++ b/components/log-viewer-webui/server/src/main.js @@ -0,0 +1,74 @@ +import dotenv from "dotenv"; +import process from "node:process"; + +import app from "./app.js"; + + +/** + * Parses environment variables into config values for the application. + * + * @return {{CLP_DB_USER: string, CLP_DB_PASS: string, HOST: string, PORT: string}} + * @throws {Error} if any required environment variable is undefined. + */ +const parseEnvVars = () => { + dotenv.config({ + path: [ + ".env.local", + ".env", + ], + }); + + /* eslint-disable sort-keys */ + const { + CLP_DB_USER, CLP_DB_PASS, HOST, PORT, + } = process.env; + const envVars = { + CLP_DB_USER, CLP_DB_PASS, HOST, PORT, + }; + /* eslint-enable sort-keys */ + + // Check for mandatory environment variables + for (const [key, value] of Object.entries(envVars)) { + if ("undefined" === typeof value) { + throw new Error(`Environment variable ${key} must be defined.`); + } + } + + return envVars; +}; + +/** + * Sets up and runs the server. + */ +const main = async () => { + const envToLogger = { + development: { + transport: { + target: "pino-pretty", + }, + }, + production: true, + test: false, + }; + + const envVars = parseEnvVars(); + const server = await app({ + fastifyOptions: { + logger: envToLogger[process.env.NODE_ENV] ?? true, + }, + sqlDbPass: envVars.CLP_DB_PASS, + sqlDbUser: envVars.CLP_DB_USER, + }); + + try { + await server.listen({host: envVars.HOST, port: Number(envVars.PORT)}); + } catch (e) { + server.log.error(e); + process.exit(1); + } +}; + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/components/log-viewer-webui/server/src/routes/example.js b/components/log-viewer-webui/server/src/routes/example.js new file mode 100644 index 000000000..fb4aa31c7 --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/example.js @@ -0,0 +1,18 @@ +/** + * Creates example routes. + * + * @param {import("fastify").FastifyInstance} fastify + * @param {import("fastify").FastifyPluginOptions} options + * @return {Promise} + */ +const routes = async (fastify, options) => { + fastify.get("/example/get/:name", async (req, resp) => { + return {msg: `Hello, ${req.params.name}!`}; + }); + + fastify.post("/example/post", async (req, resp) => { + return {msg: `Goodbye, ${req.body.name}!`}; + }); +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/routes/query.js b/components/log-viewer-webui/server/src/routes/query.js new file mode 100644 index 000000000..628bcd053 --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/query.js @@ -0,0 +1,46 @@ +// eslint-disable-next-line no-magic-numbers +const EXTRACT_IR_TARGET_UNCOMPRESSED_SIZE = 128 * 1024 * 1024; + +/** + * Creates query routes. + * + * @param {import("fastify").FastifyInstance | {dbManager: DbManager}} fastify + * @param {import("fastify").FastifyPluginOptions} options + * @return {Promise} + */ +const routes = async (fastify, options) => { + fastify.post("/query/extract-ir", async (req, resp) => { + const {origFileId, logEventIdx} = req.body; + const sanitizedLogEventIdx = Number(logEventIdx); + + let irMetadata = await fastify.dbManager.getExtractedIrFileMetadata( + origFileId, + sanitizedLogEventIdx + ); + + if (null === irMetadata) { + const extractResult = await fastify.dbManager.submitAndWaitForExtractIrJob({ + file_split_id: null, + msg_ix: sanitizedLogEventIdx, + orig_file_id: origFileId, + target_uncompressed_size: EXTRACT_IR_TARGET_UNCOMPRESSED_SIZE, + }); + + if (null === extractResult) { + const err = new Error("Unable to extract IR for file with " + + `origFileId=${origFileId} at logEventIdx=${sanitizedLogEventIdx}`); + + err.statusCode = 400; + throw err; + } + irMetadata = await fastify.dbManager.getExtractedIrFileMetadata( + origFileId, + sanitizedLogEventIdx + ); + } + + return irMetadata; + }); +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/routes/static.js b/components/log-viewer-webui/server/src/routes/static.js new file mode 100644 index 000000000..42d9048f0 --- /dev/null +++ b/components/log-viewer-webui/server/src/routes/static.js @@ -0,0 +1,56 @@ +import path from "node:path"; +import process from "node:process"; +import {fileURLToPath} from "node:url"; + +import {fastifyStatic} from "@fastify/static"; + +import settings from "../../settings.json" with {type: "json"}; + + +/** + * Creates static files serving routes. + * + * @param {import("fastify").FastifyInstance} fastify + * @param {import("fastify").FastifyPluginOptions} options + */ +const routes = async (fastify, options) => { + const filename = fileURLToPath(import.meta.url); + const dirname = path.dirname(filename); + const rootDirname = path.resolve(dirname, "../.."); + + let irFilesDir = settings.IrFilesDir; + if (false === path.isAbsolute(irFilesDir)) { + irFilesDir = path.resolve(rootDirname, irFilesDir); + } + await fastify.register(fastifyStatic, { + prefix: "/ir", + root: irFilesDir, + }); + + let logViewerDir = settings.LogViewerDir; + if (false === path.isAbsolute(logViewerDir)) { + logViewerDir = path.resolve(rootDirname, logViewerDir); + } + await fastify.register(fastifyStatic, { + prefix: "/log-viewer", + root: logViewerDir, + decorateReply: false, + }); + + if ("production" === process.env.NODE_ENV) { + // In the development environment, we expect the client to use a separate webserver that + // supports live reloading. + let clientDir = settings.ClientDir; + if (false === path.isAbsolute(clientDir)) { + clientDir = path.resolve(rootDirname, settings.ClientDir); + } + + await fastify.register(fastifyStatic, { + prefix: "/", + root: clientDir, + decorateReply: false, + }); + } +}; + +export default routes; diff --git a/components/log-viewer-webui/server/src/utils.js b/components/log-viewer-webui/server/src/utils.js new file mode 100644 index 000000000..580ea76f8 --- /dev/null +++ b/components/log-viewer-webui/server/src/utils.js @@ -0,0 +1,13 @@ +const MILLIS_PER_SECOND = 1000; + +/** + * Creates a promise that resolves after a specified number of seconds. + * + * @param {number} seconds Number of seconds to wait before resolving the promise. + * @return {Promise} A promise that resolves after the specified delay. + */ +const sleep = (seconds) => new Promise((resolve) => { + setTimeout(resolve, seconds * MILLIS_PER_SECOND); +}); + +export {sleep}; diff --git a/components/log-viewer-webui/yscope-log-viewer b/components/log-viewer-webui/yscope-log-viewer new file mode 160000 index 000000000..df996eac0 --- /dev/null +++ b/components/log-viewer-webui/yscope-log-viewer @@ -0,0 +1 @@ +Subproject commit df996eac000823d02f9cd0b9eb4bb732dd634ae5 diff --git a/components/package-template/src/etc/clp-config.yml b/components/package-template/src/etc/clp-config.yml index a9ffcc924..cb66f40cd 100644 --- a/components/package-template/src/etc/clp-config.yml +++ b/components/package-template/src/etc/clp-config.yml @@ -20,7 +20,7 @@ # jobs_poll_delay: 0.1 # seconds # logging_level: "INFO" # -#search_scheduler: +#query_scheduler: # host: "localhost" # port: 7000 # jobs_poll_delay: 0.1 # seconds @@ -34,7 +34,7 @@ #redis: # host: "localhost" # port: 6379 -# search_backend_database: 0 +# query_backend_database: 0 # compression_backend_database: 1 # #reducer: @@ -46,12 +46,13 @@ #results_cache: # host: "localhost" # port: 27017 -# db_name: "clp-search" +# db_name: "clp-query-results" +# ir_collection_name: "ir-files" # #compression_worker: # logging_level: "INFO" # -#search_worker: +#query_worker: # logging_level: "INFO" # #webui: @@ -59,6 +60,10 @@ # port: 4000 # logging_level: "INFO" # +#log_viewer_webui: +# host: "localhost" +# port: 3000 +# ## Where archives should be output to #archive_output: # directory: "var/data/archives" @@ -77,6 +82,13 @@ # # How much data CLP should try to fit into each segment within an archive # target_segment_size: 268435456 # 256 MB # +## Where CLP IR files should be output +#ir_output: +# directory: "var/data/ir" +# +# # How large each IR file should be before being split into a new IR file +# target_uncompressed_size: 134217728 # 128 MB +# ## Location where other data (besides archives) are stored. It will be created if ## it doesn't exist. #data_directory: "var/data" diff --git a/components/webui/.meteor/packages b/components/webui/.meteor/packages index 367584235..88d4b182a 100644 --- a/components/webui/.meteor/packages +++ b/components/webui/.meteor/packages @@ -4,20 +4,16 @@ # 'meteor add' and 'meteor remove' will edit this file for you, # but you can also edit it by hand. -meteor-base@1.5.1 # Packages every Meteor app needs to have -mobile-experience@1.1.1 # Packages for a great mobile UX -mongo@1.16.8 # The database Meteor supports right now -reactive-var@1.0.12 # Reactive variable for tracker +meteor-base@1.5.1 # Packages every Meteor app needs to have +mongo@1.16.10 # The database Meteor supports right now +reactive-var@1.0.12 # Reactive variable for tracker -standard-minifier-css@1.9.2 # CSS minifier run for production mode -standard-minifier-js@2.8.1 # JS minifier run for production mode -es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers -ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code -typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules -shell-server@0.5.0 # Server-side component of the `meteor shell` command -hot-module-replacement@0.5.3 # Update client in development without reloading the page +standard-minifier-css@1.9.2 # CSS minifier run for production mode +standard-minifier-js@2.8.1 # JS minifier run for production mode +ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code +hot-module-replacement@0.5.3 # Update client in development without reloading the page -static-html@1.3.2 # Define static page content in .html files -react-meteor-data # React higher-order component for reactively tracking Meteor data -fourseven:scss -meteortesting:mocha +static-html@1.3.2 # Define static page content in .html files +react-meteor-data@2.7.2 # React higher-order component for reactively tracking Meteor data +fourseven:scss@4.16.0 # Compile Sass files with node-sass +meteortesting:mocha@2.1.0 # Testing framework diff --git a/components/webui/.meteor/release b/components/webui/.meteor/release index 966586ce5..5152abe9d 100644 --- a/components/webui/.meteor/release +++ b/components/webui/.meteor/release @@ -1 +1 @@ -METEOR@2.15 +METEOR@2.16 diff --git a/components/webui/.meteor/versions b/components/webui/.meteor/versions index 036863b05..d568e975c 100644 --- a/components/webui/.meteor/versions +++ b/components/webui/.meteor/versions @@ -4,16 +4,16 @@ babel-compiler@7.10.5 babel-runtime@1.5.1 base64@1.0.12 binary-heap@1.0.11 -blaze-tools@1.1.4 +blaze-tools@1.1.3 boilerplate-generator@1.7.2 caching-compiler@1.2.2 -caching-html-compiler@1.2.2 +caching-html-compiler@1.2.1 callback-hook@1.5.1 -check@1.3.2 +check@1.4.1 ddp@1.4.1 -ddp-client@2.6.1 -ddp-common@1.4.0 -ddp-server@2.7.0 +ddp-client@2.6.2 +ddp-common@1.4.1 +ddp-server@2.7.1 diff-sequence@1.1.2 dynamic-import@0.7.3 ecmascript@0.16.8 @@ -27,28 +27,25 @@ fourseven:scss@4.16.0 geojson-utils@1.0.11 hot-code-push@1.0.4 hot-module-replacement@0.5.3 -html-tools@1.1.4 -htmljs@1.2.1 +html-tools@1.1.3 +htmljs@1.1.1 http@1.0.10 id-map@1.1.1 inter-process-messaging@0.1.1 -launch-screen@2.0.0 -logging@1.3.3 +logging@1.3.4 meteor@1.11.5 meteor-base@1.5.1 meteortesting:browser-tests@1.4.2 meteortesting:mocha@2.1.0 meteortesting:mocha-core@8.0.1 minifier-css@1.6.4 -minifier-js@2.7.5 -minimongo@1.9.3 -mobile-experience@1.1.1 -mobile-status-bar@1.1.0 +minifier-js@2.8.0 +minimongo@1.9.4 modern-browsers@0.1.10 modules@0.20.0 modules-runtime@0.13.1 modules-runtime-hot@0.14.2 -mongo@1.16.9 +mongo@1.16.10 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 @@ -62,16 +59,15 @@ reactive-var@1.0.12 reload@1.3.1 retry@1.1.0 routepolicy@1.1.1 -shell-server@0.5.0 socket-stream-client@0.5.2 -spacebars-compiler@1.3.2 +spacebars-compiler@1.3.1 standard-minifier-css@1.9.2 standard-minifier-js@2.8.1 static-html@1.3.2 -templating-tools@1.2.3 +templating-tools@1.2.2 tracker@1.3.3 typescript@4.9.5 -underscore@1.6.1 +underscore@1.6.2 url@1.3.2 webapp@1.13.8 webapp-hashing@1.1.1 diff --git a/components/webui/.meteorignore b/components/webui/.meteorignore new file mode 100644 index 000000000..48357e9f1 --- /dev/null +++ b/components/webui/.meteorignore @@ -0,0 +1 @@ +linter diff --git a/components/webui/imports/api/ingestion/server/publications.js b/components/webui/imports/api/ingestion/server/publications.js index 6a558e7fe..61aefb8a1 100644 --- a/components/webui/imports/api/ingestion/server/publications.js +++ b/components/webui/imports/api/ingestion/server/publications.js @@ -4,10 +4,13 @@ import {logger} from "/imports/utils/logger"; import {MONGO_SORT_BY_ID} from "/imports/utils/mongo"; import { - CompressionJobsCollection, STATS_COLLECTION_ID, StatsCollection, + CompressionJobsCollection, + STATS_COLLECTION_ID, + StatsCollection, } from "../collections"; import { - COMPRESSION_JOB_WAITING_STATES, COMPRESSION_JOBS_TABLE_COLUMN_NAMES, + COMPRESSION_JOB_WAITING_STATES, + COMPRESSION_JOBS_TABLE_COLUMN_NAMES, } from "../constants"; import CompressionDbManager from "./CompressionDbManager"; import StatsDbManager from "./StatsDbManager"; diff --git a/components/webui/imports/api/search/constants.js b/components/webui/imports/api/search/constants.js index 0d0be9e94..baedddb85 100644 --- a/components/webui/imports/api/search/constants.js +++ b/components/webui/imports/api/search/constants.js @@ -58,29 +58,43 @@ const isOperationInProgress = (s) => ( ); /* eslint-disable sort-keys */ -let enumSearchJobStatus; +let enumQueryJobStatus; /** - * Enum of job statuses, matching the `SearchJobStatus` class in - * `job_orchestration.search_scheduler.constants`. + * Enum of job statuses, matching the `QueryJobStatus` class in + * `job_orchestration.query_scheduler.constants`. * * @enum {number} */ -const SEARCH_JOB_STATUS = Object.freeze({ - PENDING: (enumSearchJobStatus = 0), - RUNNING: ++enumSearchJobStatus, - SUCCEEDED: ++enumSearchJobStatus, - FAILED: ++enumSearchJobStatus, - CANCELLING: ++enumSearchJobStatus, - CANCELLED: ++enumSearchJobStatus, +const QUERY_JOB_STATUS = Object.freeze({ + PENDING: (enumQueryJobStatus = 0), + RUNNING: ++enumQueryJobStatus, + SUCCEEDED: ++enumQueryJobStatus, + FAILED: ++enumQueryJobStatus, + CANCELLING: ++enumQueryJobStatus, + CANCELLED: ++enumQueryJobStatus, }); /* eslint-enable sort-keys */ -const SEARCH_JOB_STATUS_WAITING_STATES = [ - SEARCH_JOB_STATUS.PENDING, - SEARCH_JOB_STATUS.RUNNING, - SEARCH_JOB_STATUS.CANCELLING, +const QUERY_JOB_STATUS_WAITING_STATES = [ + QUERY_JOB_STATUS.PENDING, + QUERY_JOB_STATUS.RUNNING, + QUERY_JOB_STATUS.CANCELLING, ]; +/* eslint-disable sort-keys */ +let enumQueryType; +/** + * Enum of job type, matching the `QueryJobType` class in + * `job_orchestration.query_scheduler.constants`. + * + * @enum {number} + */ +const QUERY_JOB_TYPE = Object.freeze({ + SEARCH_OR_AGGREGATION: (enumQueryType = 0), + EXTRACT_IR: ++enumQueryType, +}); +/* eslint-enable sort-keys */ + /** * Enum of Mongo Collection sort orders. * @@ -112,8 +126,9 @@ export { isSearchSignalReq, isSearchSignalResp, MONGO_SORT_ORDER, - SEARCH_JOB_STATUS, - SEARCH_JOB_STATUS_WAITING_STATES, + QUERY_JOB_STATUS, + QUERY_JOB_STATUS_WAITING_STATES, + QUERY_JOB_TYPE, SEARCH_MAX_NUM_RESULTS, SEARCH_RESULTS_FIELDS, SEARCH_SIGNAL, diff --git a/components/webui/imports/api/search/server/SearchJobsDbManager.js b/components/webui/imports/api/search/server/QueryJobsDbManager.js similarity index 63% rename from components/webui/imports/api/search/server/SearchJobsDbManager.js rename to components/webui/imports/api/search/server/QueryJobsDbManager.js index df1dc27fa..835aae796 100644 --- a/components/webui/imports/api/search/server/SearchJobsDbManager.js +++ b/components/webui/imports/api/search/server/QueryJobsDbManager.js @@ -3,8 +3,9 @@ import msgpack from "@msgpack/msgpack"; import {sleep} from "/imports/utils/misc"; import { - SEARCH_JOB_STATUS, - SEARCH_JOB_STATUS_WAITING_STATES, + QUERY_JOB_STATUS, + QUERY_JOB_STATUS_WAITING_STATES, + QUERY_JOB_TYPE, } from "../constants"; @@ -14,32 +15,33 @@ import { const JOB_COMPLETION_STATUS_POLL_INTERVAL_MILLIS = 0.5; /** - * Enum of the `search_jobs` table's column names. + * Enum of the `query_jobs` table's column names. * * @enum {string} */ -const SEARCH_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ +const QUERY_JOBS_TABLE_COLUMN_NAMES = Object.freeze({ ID: "id", STATUS: "status", - SEARCH_CONFIG: "search_config", + TYPE: "type", + JOB_CONFIG: "job_config", }); /** - * Class for submitting and monitoring search jobs in the database. + * Class for submitting and monitoring query jobs in the database. */ -class SearchJobsDbManager { +class QueryJobsDbManager { #sqlDbConnPool; - #searchJobsTableName; + #queryJobsTableName; /** * @param {import("mysql2/promise").Pool} sqlDbConnPool * @param {object} tableNames - * @param {string} tableNames.searchJobsTableName + * @param {string} tableNames.queryJobsTableName */ - constructor (sqlDbConnPool, {searchJobsTableName}) { + constructor (sqlDbConnPool, {queryJobsTableName}) { this.#sqlDbConnPool = sqlDbConnPool; - this.#searchJobsTableName = searchJobsTableName; + this.#queryJobsTableName = queryJobsTableName; } /** @@ -51,10 +53,12 @@ class SearchJobsDbManager { */ async submitSearchJob (searchConfig) { const [queryInsertResults] = await this.#sqlDbConnPool.query( - `INSERT INTO ${this.#searchJobsTableName} - (${SEARCH_JOBS_TABLE_COLUMN_NAMES.SEARCH_CONFIG}) - VALUES (?)`, - [Buffer.from(msgpack.encode(searchConfig))], + `INSERT INTO ${this.#queryJobsTableName} + (${QUERY_JOBS_TABLE_COLUMN_NAMES.JOB_CONFIG}, + ${QUERY_JOBS_TABLE_COLUMN_NAMES.TYPE}) + VALUES (?, ?)`, + [Buffer.from(msgpack.encode(searchConfig)), + QUERY_JOB_TYPE.SEARCH_OR_AGGREGATION], ); return queryInsertResults.insertId; @@ -88,11 +92,11 @@ class SearchJobsDbManager { */ async submitQueryCancellation (jobId) { await this.#sqlDbConnPool.query( - `UPDATE ${this.#searchJobsTableName} - SET ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} = ${SEARCH_JOB_STATUS.CANCELLING} - WHERE ${SEARCH_JOBS_TABLE_COLUMN_NAMES.ID} = ? - AND ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} - IN (${SEARCH_JOB_STATUS.PENDING}, ${SEARCH_JOB_STATUS.RUNNING})`, + `UPDATE ${this.#queryJobsTableName} + SET ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} = ${QUERY_JOB_STATUS.CANCELLING} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? + AND ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + IN (${QUERY_JOB_STATUS.PENDING}, ${QUERY_JOB_STATUS.RUNNING})`, jobId, ); } @@ -111,9 +115,9 @@ class SearchJobsDbManager { try { const [queryRows] = await this.#sqlDbConnPool.query( ` - SELECT ${SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS} - FROM ${this.#searchJobsTableName} - WHERE ${SEARCH_JOBS_TABLE_COLUMN_NAMES.ID} = ? + SELECT ${QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS} + FROM ${this.#queryJobsTableName} + WHERE ${QUERY_JOBS_TABLE_COLUMN_NAMES.ID} = ? `, jobId, ); @@ -125,14 +129,14 @@ class SearchJobsDbManager { if (0 === rows.length) { throw new Error(`Job ${jobId} not found in database.`); } - const status = rows[0][SEARCH_JOBS_TABLE_COLUMN_NAMES.STATUS]; + const status = rows[0][QUERY_JOBS_TABLE_COLUMN_NAMES.STATUS]; - if (false === SEARCH_JOB_STATUS_WAITING_STATES.includes(status)) { - if (SEARCH_JOB_STATUS.CANCELLED === status) { + if (false === QUERY_JOB_STATUS_WAITING_STATES.includes(status)) { + if (QUERY_JOB_STATUS.CANCELLED === status) { throw new Error(`Job ${jobId} was cancelled.`); - } else if (SEARCH_JOB_STATUS.SUCCEEDED !== status) { + } else if (QUERY_JOB_STATUS.SUCCEEDED !== status) { throw new Error(`Job ${jobId} exited with unexpected status=${status}: ` + - `${Object.keys(SEARCH_JOB_STATUS)[status]}.`); + `${Object.keys(QUERY_JOB_STATUS)[status]}.`); } break; } @@ -142,4 +146,4 @@ class SearchJobsDbManager { } } -export default SearchJobsDbManager; +export default QueryJobsDbManager; diff --git a/components/webui/imports/api/search/server/methods.js b/components/webui/imports/api/search/server/methods.js index b0b6a5851..695b6b4a6 100644 --- a/components/webui/imports/api/search/server/methods.js +++ b/components/webui/imports/api/search/server/methods.js @@ -9,24 +9,24 @@ import { } from "../constants"; import {ERROR_NAME_COLLECTION_DROPPED} from "../SearchJobCollectionsManager"; import {searchJobCollectionsManager} from "./collections"; -import SearchJobsDbManager from "./SearchJobsDbManager"; +import QueryJobsDbManager from "./QueryJobsDbManager"; /** - * @type {SearchJobsDbManager|null} + * @type {QueryJobsDbManager|null} */ -let searchJobsDbManager = null; +let queryJobsDbManager = null; /** - * Initializes the SearchJobsDbManager. + * Initializes the QueryJobsDbManager. * * @param {import("mysql2/promise").Pool} sqlDbConnPool * @param {object} tableNames - * @param {string} tableNames.searchJobsTableName + * @param {string} tableNames.queryJobsTableName * @throws {Error} on error. */ -const initSearchJobsDbManager = (sqlDbConnPool, {searchJobsTableName}) => { - searchJobsDbManager = new SearchJobsDbManager(sqlDbConnPool, {searchJobsTableName}); +const initQueryJobsDbManager = (sqlDbConnPool, {queryJobsTableName}) => { + queryJobsDbManager = new QueryJobsDbManager(sqlDbConnPool, {queryJobsTableName}); }; /** @@ -67,8 +67,8 @@ const updateSearchSignalWhenJobsFinish = async ({ }) => { let errorMsg; try { - await searchJobsDbManager.awaitJobCompletion(searchJobId); - await searchJobsDbManager.awaitJobCompletion(aggregationJobId); + await queryJobsDbManager.awaitJobCompletion(searchJobId); + await queryJobsDbManager.awaitJobCompletion(aggregationJobId); } catch (e) { errorMsg = e.message; } @@ -169,9 +169,9 @@ Meteor.methods({ let searchJobId; let aggregationJobId; try { - searchJobId = await searchJobsDbManager.submitSearchJob(args); + searchJobId = await queryJobsDbManager.submitSearchJob(args); aggregationJobId = - await searchJobsDbManager.submitAggregationJob(args, timeRangeBucketSizeMillis); + await queryJobsDbManager.submitAggregationJob(args, timeRangeBucketSizeMillis); } catch (e) { const errorMsg = "Unable to submit search/aggregation job to the SQL database."; logger.error(errorMsg, e.toString()); @@ -241,8 +241,8 @@ Meteor.methods({ `aggregationJobId=${aggregationJobId}`); try { - await searchJobsDbManager.submitQueryCancellation(searchJobId); - await searchJobsDbManager.submitQueryCancellation(aggregationJobId); + await queryJobsDbManager.submitQueryCancellation(searchJobId); + await queryJobsDbManager.submitQueryCancellation(aggregationJobId); updateSearchResultsMeta({ jobId: searchJobId, lastSignal: SEARCH_SIGNAL.RESP_QUERYING, @@ -260,4 +260,4 @@ Meteor.methods({ }, }); -export {initSearchJobsDbManager}; +export {initQueryJobsDbManager}; diff --git a/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx b/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx index 7f763ac19..382212ad6 100644 --- a/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx +++ b/components/webui/imports/ui/SearchView/SearchControls/SearchControlsFilterDrawer/SearchControlsTimeRangeInput/index.jsx @@ -8,7 +8,9 @@ import Row from "react-bootstrap/Row"; import { computeTimeRange, convertLocalDateToSameUtcDatetime, - convertUtcDatetimeToSameLocalDate, TIME_RANGE_PRESET_LABEL, TIME_UNIT, + convertUtcDatetimeToSameLocalDate, + TIME_RANGE_PRESET_LABEL, + TIME_UNIT, } from "/imports/utils/datetime"; import SearchControlsFilterLabel from "../SearchControlsFilterLabel"; diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx index 9c9c0e97c..b7f62e1d8 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsLoadSensor.jsx @@ -1,5 +1,6 @@ import { - useEffect, useRef, + useEffect, + useRef, } from "react"; import Spinner from "react-bootstrap/Spinner"; diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss index 6c24ec2da..db2f38117 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/SearchResultsTable.scss @@ -27,6 +27,8 @@ } .search-results-content { + overflow: auto; + font-size: 0.875rem; line-height: var(--search-results-message-line-height); } diff --git a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx index bbdf11ca8..28a331bb8 100644 --- a/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx +++ b/components/webui/imports/ui/SearchView/SearchResults/SearchResultsTable/index.jsx @@ -1,15 +1,20 @@ +import {Meteor} from "meteor/meteor"; import {useEffect} from "react"; import Table from "react-bootstrap/Table"; import dayjs from "dayjs"; import { - faSort, faSortDown, faSortUp, + faSort, + faSortDown, + faSortUp, + faSquareUpRight, } from "@fortawesome/free-solid-svg-icons"; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; import { - MONGO_SORT_ORDER, SEARCH_RESULTS_FIELDS, + MONGO_SORT_ORDER, + SEARCH_RESULTS_FIELDS, } from "/imports/api/search/constants"; import {DATETIME_FORMAT_TEMPLATE} from "/imports/utils/datetime"; @@ -32,7 +37,7 @@ const SEARCH_RESULT_MESSAGE_LINE_HEIGHT = 1.5; * @param {boolean} props.hasMoreResultsInTotal * @param {number} props.maxLinesPerResult * @param {Function} props.onLoadMoreResults - * @param {object} props.searchResults + * @param {object[]} props.searchResults * @param {Function} props.setFieldToSortBy * @return {React.ReactElement} */ @@ -88,24 +93,9 @@ const SearchResultsTable = ({ ); }, [maxLinesPerResult]); - const rows = []; - for (let i = 0; i < searchResults.length; ++i) { - const searchResult = searchResults[i]; - rows.push( - - - {searchResult.timestamp ? - dayjs.utc(searchResult.timestamp).format(DATETIME_FORMAT_TEMPLATE) : - "N/A"} - - -
-                        {searchResult.message}
-                    
- - - ); - } + // eslint-disable-next-line no-warning-comments + // TODO: remove this flag once "Extract IR" support is added for ClpStorageEngine "clp-s" + const isExtractIrSupported = ("clp" === Meteor.settings.public.ClpStorageEngine); return (
@@ -120,7 +110,6 @@ const SearchResultsTable = ({
@@ -131,16 +120,45 @@ const SearchResultsTable = ({
Log message
+ {isExtractIrSupported && + +
 
+ } - {rows} + {searchResults.map((result) => ( + + + {result.timestamp ? + dayjs.utc(result.timestamp).format(DATETIME_FORMAT_TEMPLATE) : + "N/A"} + + +
+                                    {result.message}
+                                
+ + {isExtractIrSupported && + + + + + } + + ))} } * @throws {Error} on error. */ @@ -46,7 +46,7 @@ const initDbManagers = async ({ clpArchivesTableName, clpFilesTableName, compressionJobsTableName, - searchJobsTableName, + queryJobsTableName, }) => { if (null !== dbConnPool) { throw Error("This method should not be called twice."); @@ -76,8 +76,8 @@ const initDbManagers = async ({ initCompressionDbManager(dbConnPool, { compressionJobsTableName, }); - initSearchJobsDbManager(dbConnPool, { - searchJobsTableName, + initQueryJobsDbManager(dbConnPool, { + queryJobsTableName, }); initStatsDbManager(dbConnPool, { clpArchivesTableName, diff --git a/components/webui/server/main.js b/components/webui/server/main.js index 93992d1d3..2660e16c1 100644 --- a/components/webui/server/main.js +++ b/components/webui/server/main.js @@ -67,7 +67,7 @@ Meteor.startup(async () => { clpArchivesTableName: Meteor.settings.private.SqlDbClpArchivesTableName, clpFilesTableName: Meteor.settings.private.SqlDbClpFilesTableName, compressionJobsTableName: Meteor.settings.private.SqlDbCompressionJobsTableName, - searchJobsTableName: Meteor.settings.private.SqlDbSearchJobsTableName, + queryJobsTableName: Meteor.settings.private.SqlDbQueryJobsTableName, }); }); diff --git a/components/webui/settings.json b/components/webui/settings.json index 7cf574b32..f959dac8e 100644 --- a/components/webui/settings.json +++ b/components/webui/settings.json @@ -7,12 +7,13 @@ "SqlDbClpArchivesTableName": "clp_archives", "SqlDbClpFilesTableName": "clp_files", "SqlDbCompressionJobsTableName": "compression_jobs", - "SqlDbSearchJobsTableName": "search_jobs" + "SqlDbQueryJobsTableName": "query_jobs" }, "public": { "AggregationResultsCollectionName": "aggregation-results", "ClpStorageEngine": "clp", "CompressionJobsCollectionName": "compression-jobs", + "LogViewerWebuiUrl": "http://localhost:8080", "SearchResultsCollectionName": "search-results", "SearchResultsMetadataCollectionName": "results-metadata", "StatsCollectionName": "stats", diff --git a/docs/src/clp-complete-solution.png b/docs/src/clp-complete-solution.png index a7dece4c2..6672ef123 100644 --- a/docs/src/clp-complete-solution.png +++ b/docs/src/clp-complete-solution.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1a9db81627e43da8588028ec995d845dbb68c8157a622252f8fb9173e5196fe -size 155065 +oid sha256:dfd115b220846dcd754539bd90254d62c757aa5e4de559af6604144933945d0d +size 216366 diff --git a/docs/src/dev-guide/components-core/index.md b/docs/src/dev-guide/components-core/index.md index 0e9fd2623..62360486a 100644 --- a/docs/src/dev-guide/components-core/index.md +++ b/docs/src/dev-guide/components-core/index.md @@ -91,6 +91,7 @@ centos7.4-deps-install macos12-deps-install ubuntu-focal-deps-install ubuntu-jammy-deps-install +regex-utils ::: [feature-req]: https://github.com/y-scope/clp/issues/new?assignees=&labels=enhancement&template=feature-request.yml diff --git a/docs/src/dev-guide/components-core/regex-utils.md b/docs/src/dev-guide/components-core/regex-utils.md new file mode 100644 index 000000000..b79f0a729 --- /dev/null +++ b/docs/src/dev-guide/components-core/regex-utils.md @@ -0,0 +1,101 @@ +# regex_utils library + +This library contains useful utilities to handle all regex related tasks. + +## Regex to Wildcard Translator + +### Goal + +Performs a best-effort translation to turn a regex string to an equivalent wildcard string. + +CLP currently only recognizes three meta-characters in the wildcard syntax: + +* `?` Matches any single character +* `*` Matches zero or more characters +* `\` Suppresses the special meaning of meta characters (including itself) + +If the regex query can actually be expressed as a wildcard query only deploying the three +metacharacters above, CLP should use the wildcard version. + +### Includes + +* The translator function returns a `Result` type, which can either +contain a value or an error code. + +To use the translator: + +```cpp +#include + +using clp::regex_utils::regex_to_wildcard; + +// Other code + +auto result{regex_to_wildcard(wildcard_str)}; +if (result.has_error()) { + auto err_code{result.error()}; + // Handle error +} else { + auto regex_str{result.value()}; + // Do things with the translated string +} +``` + +* To add custom configuration to the translator: + +```cpp +#include + +RegexToWildcardTranslatorConfig config{true, false, /*...other booleans*/}; +auto result{regex_to_wildcard(wildcard_str, config)}; + +// Same as above +``` + +For a detailed description on the options order and usage, see the +[Custom Configuration](#custom-configuration) section. + +### Functionalities + +* Wildcards + * Turn `.` into `?` + * Turn `.*` into `*` + * Turn `.+` into `?*` + * E.g. `abc.*def.ghi.+` will get translated to `abc*def?ghi?*` +* Metacharacter escape sequences + * An escaped regex metacharacter is treated as a literal and appended to the wildcard output. + * The list of characters that require escaping to have their special meanings suppressed is + `[\/^$.|?*+(){}`. + * Superfluous escape characters are ignored for the following characters: `],<>-_=!`. + * E.g. `a\[\+b\-\_c-_d` will get translated to `a[+b-_c-_d` + * Note: generally, any non-alphanumeric character can be escaped to use it as a literal. The + list this utils library supports is non-exhaustive and can be expanded when necessary. + * For metacharacters shared by both syntaxes, keep the escape backslashes. + * The list of characters that fall into this category is `*?\`. All wildcard metacharacters are + also regex metacharacters. + * E.g. `a\*b\?c\\d` will get translated to `a\*b\?c\\d` (no change) + * Escape sequences with alphanumeric characters are disallowed. + * E.g. Special utility escape sequences `\Q`, `\E`, `\A` etc. and back references `\1` `\2` etc. + cannot be translated. +* Character set + * Reduces a character set into a single character if possible. + * A trivial character set containing a single character or a single escaped metacharacter. + * E.g. `[a]` into `a`, `[\^]` into `^` + * If the `case_insensitive_wildcard` config is turned on, the translator can also reduce the + case-insensitive style character set patterns into a single lowercase character: + * E.g. `[aA]` into `a`, `[Bb]` into `b`, `[xX][Yy][zZ]` into `xyz` + +### Custom configuration + +The `RegexToWildcardTranslatorConfig` class objects are currently immutable once instantiated. By +default, all of the options are set to `false`. + +The constructor takes the following option arguments in order: + +* `case_insensitive_wildcard`: see **Character set** bullet point in the + [Functionalities](#functionalities) section. + +* `add_prefix_suffix_wildcards`: in the absence of regex anchors, add prefix or suffix wildcards so + the query becomes a substring query. + * E.g. `info.*system` gets translated into `*info*system*` which makes the original query a + substring query. diff --git a/docs/src/dev-guide/components-log-viewer-webui.md b/docs/src/dev-guide/components-log-viewer-webui.md new file mode 100644 index 000000000..463d67c78 --- /dev/null +++ b/docs/src/dev-guide/components-log-viewer-webui.md @@ -0,0 +1,86 @@ +# Log Viewer WebUI + +A webapp that allows us to serve the [log-viewer] and integrate it with CLP's [webui]. The webapp +currently consists of a [React] client and a [Fastify] server. + +## Requirements + +* Node.js v20 or higher + +## Setup + +Install the app's dependencies: + +```shell +cd components/log-viewer-webui +(cd client && npm i) +(cd server && npm i) +``` + +## Running + +To run the client during development: + +```shell +npm run start +``` + +To run the server during development: + +```shell +npm run start +``` + +To run the server in production: + +```shell +npm run prod +``` + +In both cases, if you want to customize what host and port the server binds to, you can use the +environment variables in `components/log-viewer-webui/server/.env`. + +## Testing + +To run the server's unit tests: + +```shell +npm test +``` + +## Linting + +You can lint this component either as part of the entire project or as a standalone component. + +### Lint as part of the project + +To check for linting errors: + +```shell +task lint:js-check +``` + +To also fix linting errors (if applicable): + +```shell +task lint:js-fix +``` + +### Lint the component alone + +To check for linting errors: + +```shell +npm run lint:check +``` + +To also fix linting errors (if applicable): + +```shell +npm run lint:fix +``` + +[Fastify]: https://www.fastify.io/ +[log-viewer]: https://github.com/y-scope/yscope-log-viewer +[React]: https://reactjs.org/ +[webui]: components-webui.md diff --git a/docs/src/dev-guide/components-webui.md b/docs/src/dev-guide/components-webui.md index bf2f76f6f..c5a482e12 100644 --- a/docs/src/dev-guide/components-webui.md +++ b/docs/src/dev-guide/components-webui.md @@ -41,7 +41,7 @@ package: ```shell # Please update `` accordingly. - MONGO_URL="mongodb://localhost:27017/clp-search" \ + MONGO_URL="mongodb://localhost:27017/clp-query-results" \ ROOT_URL="http://localhost:4000" \ CLP_DB_USER="clp-user" \ CLP_DB_PASS="" \ diff --git a/docs/src/dev-guide/index.md b/docs/src/dev-guide/index.md index 237cf1df0..1dbf560a0 100644 --- a/docs/src/dev-guide/index.md +++ b/docs/src/dev-guide/index.md @@ -63,6 +63,7 @@ contributing-linting :hidden: components-core/index +components-log-viewer-webui components-webui ::: diff --git a/docs/src/user-guide/core-clp-s.md b/docs/src/user-guide/core-clp-s.md index b48983f0c..8528f98d2 100644 --- a/docs/src/user-guide/core-clp-s.md +++ b/docs/src/user-guide/core-clp-s.md @@ -14,7 +14,8 @@ Usage: * `archives-dir` is the directory that archives should be written to. * `input-path` is any new-line-delimited JSON (ndjson) log file or directory containing such files. * `options` allow you to specify things like which field should be considered as the log event's - timestamp (`--timestamp-key `). + timestamp (`--timestamp-key `), or whether to fully parse array entries and encode + them into dedicated columns (`--structurize-arrays`). * For a complete list, run `./clp-s c --help` ### Examples diff --git a/docs/src/user-guide/quick-start-cluster-setup/multi-node.md b/docs/src/user-guide/quick-start-cluster-setup/multi-node.md index aedc6c457..705f2f97a 100644 --- a/docs/src/user-guide/quick-start-cluster-setup/multi-node.md +++ b/docs/src/user-guide/quick-start-cluster-setup/multi-node.md @@ -22,15 +22,15 @@ worker components. The tables below list the components and their functions. :::{table} Controller components :align: left -| Component | Description | -|-----------------------|------------------------------------------------------------------| -| database | Database for archive metadata, compression jobs, and search jobs | -| queue | Task queue for schedulers | -| redis | Task result storage for workers | -| compression_scheduler | Scheduler for compression jobs | -| search_scheduler | Scheduler for search jobs | -| results_cache | Storage for the workers to return search results to the UI | -| webui | Web server for the UI | +| Component | Description | +|-----------------------|-----------------------------------------------------------------| +| database | Database for archive metadata, compression jobs, and query jobs | +| queue | Task queue for schedulers | +| redis | Task result storage for workers | +| compression_scheduler | Scheduler for compression jobs | +| query_scheduler | Scheduler for search/aggregation jobs | +| results_cache | Storage for the workers to return search results to the UI | +| webui | Web server for the UI | ::: :::{table} Worker components @@ -39,12 +39,12 @@ worker components. The tables below list the components and their functions. | Component | Description | |--------------------|--------------------------------------------------------------| | compression_worker | Worker processes for compression jobs | -| search_worker | Worker processes for search/aggregation jobs | +| query_worker | Worker processes for search/aggregation jobs | | reducer | Reducers for performing the final stages of aggregation jobs | ::: :::{note} -Running additional workers increases the parallelism of compression and search jobs. +Running additional workers increases the parallelism of compression and search/aggregation jobs. ::: ## Configuring CLP @@ -92,12 +92,12 @@ but all components in a group must be started before starting a component in the **Group 2 components:** * `compression_scheduler` -* `search_scheduler` +* `query_scheduler` **Group 3 components:** * `compression_worker` -* `search_worker` +* `query_worker` * `reducer` For each component, on the host where you want to run the component, run: diff --git a/docs/src/user-guide/reference-json-search-syntax.md b/docs/src/user-guide/reference-json-search-syntax.md index 416bbd264..ca6898984 100644 --- a/docs/src/user-guide/reference-json-search-syntax.md +++ b/docs/src/user-guide/reference-json-search-syntax.md @@ -139,8 +139,10 @@ parent1: {parent2: {child: value}} ``` :::{caution} -CLP does not currently support queries for array kv-pairs where only part of the key is known. In +By default, CLP does not support queries for array kv-pairs where only part of the key is known. In other words, the key must either be a wildcard (`*`) or it must contain no wildcards. + +Archives compressed using the `--structurize-arrays` flag *do not* have this limitation. ::: ### Complex queries diff --git a/docs/tasks.yml b/docs/tasks.yml index 05ec8aeef..45e2cf05e 100644 --- a/docs/tasks.yml +++ b/docs/tasks.yml @@ -25,7 +25,7 @@ tasks: dir: "{{.TASKFILE_DIR}}" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -34,16 +34,20 @@ tasks: # Call `clean` before building since `sphinx-build --write-all --fresh-env` isn't always # equivalent to building from scratch. - task: "clean" + - "python3 '{{.ROOT_DIR}}/tools/scripts/find-broken-docs-links.py'" - |- . "{{.G_DOCS_VENV_DIR}}/bin/activate" sphinx-build \ --write-all \ --fresh-env \ --conf-dir conf \ + --nitpicky \ + --fail-on-warning \ + --keep-going \ --builder html \ src "{{.OUTPUT_DIR}}" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" @@ -63,18 +67,18 @@ tasks: REQUIREMENTS_FILE: "docs/requirements.txt" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: ":create-venv" + - task: ":utils:create-venv" vars: LABEL: "docs" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "{{.REQUIREMENTS_FILE}}" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" @@ -91,7 +95,7 @@ tasks: OUTPUT_DIR: "{{.G_NODE_DEPS_DIR}}" deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" @@ -99,7 +103,7 @@ tasks: - "rm -rf '{{.OUTPUT_DIR}}'" - "npm --prefix '{{.OUTPUT_DIR}}' install http-server" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" diff --git a/lint-tasks.yml b/lint-tasks.yml index ba5c30fbf..9f21cd8ed 100644 --- a/lint-tasks.yml +++ b/lint-tasks.yml @@ -1,9 +1,8 @@ version: "3" vars: - G_LINTER_NODEJS_BUILD_DIR: "{{.G_BUILD_DIR}}/linter-nodejs" - G_LINTER_NODEJS_BIN_DIR: "{{.G_LINTER_NODEJS_BUILD_DIR}}/bin" G_LINT_VENV_DIR: "{{.G_BUILD_DIR}}/lint-venv" + G_WEBUI_SRC_DIR: "{{.ROOT_DIR}}/components/webui" tasks: check: @@ -21,11 +20,6 @@ tasks: - task: "yml-fix" cpp-check: - dir: "components/core" - cmds: - - task: "cpp" - vars: - FLAGS: "--dry-run" sources: &cpp_source_files - "{{.TASKFILE}}" - ".clang-format" @@ -37,44 +31,55 @@ tasks: - "tests/**/*.h" - "tests/**/*.hpp" - "tests/**/*.inc" + dir: "components/core" + cmds: + - task: "cpp" + vars: + FLAGS: "--dry-run" cpp-fix: + sources: *cpp_source_files dir: "components/core" cmds: - task: "cpp" vars: FLAGS: "-i" - sources: *cpp_source_files js-check: - dir: "components/webui" - cmds: - - task: "js" - vars: - LINT_CMD: "check" sources: &js_source_files - "{{.G_BUILD_DIR}}/lint#linter-node-modules.md5" + - "{{.G_BUILD_DIR}}/log-viewer-webui-node-modules.md5" - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/**/*.css" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/**/*.jsx" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/client/src/webpack.config.js" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/package.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/settings.json" + - "{{.G_LOG_VIEWER_WEBUI_SRC_DIR}}/server/src/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/client/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/client/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/imports/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/imports/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/launcher.js" + - "{{.G_WEBUI_SRC_DIR}}/package.json" + - "{{.G_WEBUI_SRC_DIR}}/server/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/server/**/*.jsx" + - "{{.G_WEBUI_SRC_DIR}}/tests/**/*.js" + - "{{.G_WEBUI_SRC_DIR}}/tests/**/*.jsx" - "{{.ROOT_DIR}}/Taskfile.yml" - "{{.TASKFILE}}" - - "client/**/*.js" - - "client/**/*.jsx" - - "imports/**/*.js" - - "imports/**/*.jsx" - - "launcher.js" - - "package.json" - - "server/**/*.js" - - "server/**/*.jsx" - - "tests/**/*.js" - - "tests/**/*.jsx" + cmds: + - task: "js" + vars: + LINT_CMD: "check" js-fix: - dir: "components/webui" + sources: *js_source_files cmds: - task: "js" vars: LINT_CMD: "fix" - sources: *js_source_files py-check: cmds: @@ -112,8 +117,8 @@ tasks: internal: true requires: vars: ["FLAGS"] - deps: ["venv"] dir: "components/core" + deps: ["venv"] cmds: - |- . "{{.G_LINT_VENV_DIR}}/bin/activate" @@ -127,10 +132,14 @@ tasks: internal: true requires: vars: ["LINT_CMD"] - deps: ["linter-node-modules"] - dir: "components/webui" + deps: [":log-viewer-webui-node-modules", "linter-node-modules"] cmds: - - "PATH='{{.G_LINTER_NODEJS_BIN_DIR}}':$PATH npm run 'lint:{{.LINT_CMD}}'" + - for: + - "components/log-viewer-webui" + - "components/webui" + cmd: |- + cd "{{.ITEM}}" + PATH="{{.G_NODEJS_22_BIN_DIR}}":$PATH npm run "lint:{{.LINT_CMD}}" py: internal: true @@ -149,73 +158,61 @@ tasks: black --color --line-length 100 {{.BLACK_FLAGS}} . ruff check {{.RUFF_FLAGS}} . - linter-nodejs: + linter-node-modules: internal: true vars: + WEBUI_LINTER_DIR: "{{.ROOT_DIR}}/components/webui/linter" + OUTPUT_DIR: "{{.WEBUI_LINTER_DIR}}/node_modules" CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" - OUTPUT_DIR: "{{.G_LINTER_NODEJS_BUILD_DIR}}" - cmds: - - task: ":nodejs" - vars: - CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" - NODEJS_VERSION: "latest" - OUTPUT_DIR: "{{.OUTPUT_DIR}}" - - linter-node-modules: - internal: true + sources: + - "{{.G_BUILD_DIR}}/nodejs-22.md5" + - "{{.G_BUILD_DIR}}/webui-node-modules.md5" + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + - "../package.json" + dir: "{{.WEBUI_LINTER_DIR}}" + generates: ["{{.CHECKSUM_FILE}}"] deps: - ":init" - ":webui-node-modules" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" - - "linter-nodejs" - dir: "{{.WEBUI_LINTER_DIR}}" - vars: - WEBUI_LINTER_DIR: "{{.ROOT_DIR}}/components/webui/linter" - OUTPUT_DIR: "{{.WEBUI_LINTER_DIR}}/node_modules" - CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" + - ":nodejs-22" cmds: - "rm -rf '{{.OUTPUT_DIR}}'" - - "PATH='{{.G_LINTER_NODEJS_BIN_DIR}}':$PATH npm update" + - "PATH='{{.G_NODEJS_22_BIN_DIR}}':$PATH npm update" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.G_BUILD_DIR}}/lint#linter-nodejs.md5" - - "{{.G_BUILD_DIR}}/webui-node-modules.md5" - - "{{.ROOT_DIR}}/Taskfile.yml" - - "{{.TASKFILE}}" - - "../package.json" - generates: ["{{.CHECKSUM_FILE}}"] venv: internal: true vars: CHECKSUM_FILE: "{{.G_BUILD_DIR}}/{{.TASK | replace \":\" \"#\"}}.md5" OUTPUT_DIR: "{{.G_LINT_VENV_DIR}}" + sources: + - "{{.ROOT_DIR}}/Taskfile.yml" + - "{{.TASKFILE}}" + - "lint-requirements.txt" + generates: ["{{.CHECKSUM_FILE}}"] deps: - ":init" - - task: ":validate-checksum" + - task: ":utils:validate-checksum" vars: CHECKSUM_FILE: "{{.CHECKSUM_FILE}}" DATA_DIR: "{{.OUTPUT_DIR}}" cmds: - - task: ":create-venv" + - task: ":utils:create-venv" vars: LABEL: "lint" OUTPUT_DIR: "{{.OUTPUT_DIR}}" REQUIREMENTS_FILE: "lint-requirements.txt" # This command must be last - - task: ":compute-checksum" + - task: ":utils:compute-checksum" vars: DATA_DIR: "{{.OUTPUT_DIR}}" OUTPUT_FILE: "{{.CHECKSUM_FILE}}" - sources: - - "{{.ROOT_DIR}}/Taskfile.yml" - - "{{.TASKFILE}}" - - "lint-requirements.txt" - generates: ["{{.CHECKSUM_FILE}}"] diff --git a/tools/scripts/find-broken-docs-links.py b/tools/scripts/find-broken-docs-links.py new file mode 100644 index 000000000..349e927ef --- /dev/null +++ b/tools/scripts/find-broken-docs-links.py @@ -0,0 +1,117 @@ +import os +import subprocess +import sys +from pathlib import Path + + +def main(argv): + repo_root = _get_repo_root() + + found_violation = False + + # Check for docs.yscope.com links with ".md" suffixes + if _check_tracked_files( + r"docs\.yscope\.com/.+\.md", + repo_root, + repo_root, + "docs.yscope.com links cannot have \".md\" suffixes." + ): + found_violation = True + + # Check for sphinx :link: attributes that have ".md" suffixes + if _check_tracked_files( + r":link:[[:space:]]*.+\.md", + repo_root, + repo_root / "docs", + "sphinx :link: attributes cannot have \".md\" suffixes" + ): + found_violation = True + + if found_violation: + return 1 + + return 0 + + +def _get_repo_root() -> Path: + path_str = subprocess.check_output( + ["git", "rev-parse", "--show-toplevel"], + cwd=Path(__file__).parent, + text=True + ) + return Path(path_str.strip()) + + +def _check_tracked_files( + pattern: str, + repo_root: Path, + dir_to_search: Path, + error_msg: str +) -> bool: + """ + Check for a pattern in all tracked files in the repo (except this script). + :param pattern: The pattern to search for. + :param repo_root: The root of the repository. + :param dir_to_search: The directory to search in. + :param error_msg: Error message if the pattern is found. + :return: Whether the pattern was found in any file. + """ + found_matches = False + + # NOTE: "-z" ensures the paths won't be quoted (while delimiting them using '\0') + for path_str in subprocess.check_output( + [ + "git", + "ls-files", + "--cached", + "--exclude-standard", + "-z", + str(dir_to_search.relative_to(repo_root)) + ], + cwd=repo_root, + text=True, + ).split("\0"): + path = Path(path_str) + + # Skip directories and this script + if path == __file__ or (repo_root / path).is_dir(): + continue + + try: + for match in subprocess.check_output( + [ + "grep", + "--extended-regexp", + "--line-number", + "--with-filename", + pattern, + path + ], + cwd=repo_root, + text=True, + ).splitlines(): + _parse_and_print_match(match, error_msg) + found_matches = True + except subprocess.CalledProcessError: + pass + + return found_matches + + +def _parse_and_print_match(match: str, error_msg: str): + """ + Parses and prints grep matches in a format relevant to the current environment. + :param match: The match to parse and print. + :param error_msg: Error message if the pattern is found. + """ + if os.getenv("GITHUB_ACTIONS") == "true": + # Print a GitHub Actions error annotation + file, line, _ = match.split(":", 2) + print(f"::error file={file},line={line}::{error_msg}") + else: + print(error_msg, file=sys.stderr) + print(match, file=sys.stderr) + + +if "__main__" == __name__: + sys.exit(main(sys.argv)) diff --git a/tools/yscope-dev-utils b/tools/yscope-dev-utils new file mode 160000 index 000000000..ff1611e6f --- /dev/null +++ b/tools/yscope-dev-utils @@ -0,0 +1 @@ +Subproject commit ff1611e6f9b116da27dc7f8f71797829c22d0b1a