diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 0e5ce7f6c..e95235992 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. -ARG VARIANT="16-bullseye" +ARG VARIANT="20-bookworm" FROM mcr.microsoft.com/devcontainers/typescript-node:1-${VARIANT} RUN mkdir -p /workspaces && chown node:node /workspaces diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 000000000..53c5a53c2 --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,9 @@ +{ + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "2.16.1", + "resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:ce078b7bf7d9ef3bcb9813b32103795d8d72172446890b64772cbe1dec6baafd", + "integrity": "sha256:ce078b7bf7d9ef3bcb9813b32103795d8d72172446890b64772cbe1dec6baafd" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 7209d3cb6..974aaedbb 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -7,7 +7,7 @@ "build": { "dockerfile": "Dockerfile", "args": { - "VARIANT": "18-bookworm" + "VARIANT": "20-bookworm" } }, "mounts": [ @@ -27,7 +27,8 @@ "vscode": { "extensions": [ "dbaeumer.vscode-eslint", - "GitHub.vscode-pull-request-github" + "GitHub.vscode-pull-request-github", + "hbenl.vscode-mocha-test-adapter" ] }, "codespaces": { diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4197b94e5..000000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -**/node_modules/** \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 52b265e31..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,64 +0,0 @@ -module.exports = { - 'env': { - 'browser': true, - 'node': true - }, - 'parser': '@typescript-eslint/parser', - 'parserOptions': { - 'sourceType': 'module' - }, - 'plugins': [ - '@typescript-eslint', - '@stylistic' - ], - 'rules': { - // '@typescript-eslint/class-name-casing': 'warn', https://github.com/typescript-eslint/typescript-eslint/issues/2077 - '@stylistic/member-delimiter-style': [ - 'warn', - { - 'multiline': { - 'delimiter': 'semi', - 'requireLast': true - }, - 'singleline': { - 'delimiter': 'semi', - 'requireLast': false - } - } - ], - 'semi': [ - 'warn', - 'always' - ], - 'constructor-super': 'warn', - 'curly': 'warn', - 'eqeqeq': [ - 'warn', - 'always' - ], - 'no-async-promise-executor': 'warn', - 'no-buffer-constructor': 'warn', - 'no-caller': 'warn', - 'no-debugger': 'warn', - 'no-duplicate-case': 'warn', - 'no-duplicate-imports': 'warn', - 'no-eval': 'warn', - 'no-extra-semi': 'warn', - 'no-new-wrappers': 'warn', - 'no-redeclare': 'off', - 'no-sparse-arrays': 'warn', - 'no-throw-literal': 'warn', - 'no-unsafe-finally': 'warn', - 'no-unused-labels': 'warn', - '@typescript-eslint/no-redeclare': 'warn', - 'code-no-unexternalized-strings': 'warn', - 'no-throw-literal': 'warn', - 'no-var': 'warn', - 'code-no-unused-expressions': [ - 'warn', - { - 'allowTernary': true - } - ], - } -}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bfecf0a18..66f323861 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,21 +17,15 @@ updates: patterns: - "*" ignore: - - dependency-name: "@stylistic/eslint-plugin" - update-types: ["version-update:semver-major"] # stylistic 3 to avoid esm - dependency-name: "@types/chai" update-types: ["version-update:semver-major"] # chai 4 to avoid esm - dependency-name: "@types/node" - update-types: ["version-update:semver-major"] # Keep Node 18 compatibility - - dependency-name: "@types/tar" - update-types: ["version-update:semver-major"] # tar 6 for source compatibility + update-types: ["version-update:semver-major"] # Keep Node 20 compatibility - dependency-name: "chai" update-types: ["version-update:semver-major"] # chai 4 to avoid esm - - dependency-name: "eslint" - update-types: ["version-update:semver-major"] # eslint 8 for `--rulesdir` - - dependency-name: "rimraf" - update-types: ["version-update:semver-major"] # rimraf 5 for Node 18 compatibility - - dependency-name: "tar" - update-types: ["version-update:semver-major"] # tar 6 for source compatibility + - dependency-name: "yargs" + update-types: ["version-update:semver-major"] # yargs 17 for esbuild CJS bundling + - dependency-name: "node-pty" + update-types: ["version-update:semver-minor"] # node-pty 1.1 has broken spawn-helper permissions (microsoft/node-pty#866) schedule: interval: "weekly" diff --git a/.github/workflows/dev-containers.yml b/.github/workflows/dev-containers.yml index 2fa3bae1f..930d77d31 100644 --- a/.github/workflows/dev-containers.yml +++ b/.github/workflows/dev-containers.yml @@ -8,15 +8,20 @@ on: branches: - '**' +permissions: + contents: read + packages: read + jobs: cli: name: CLI runs-on: ubuntu-latest + timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://npm.pkg.github.com' scope: '@microsoft' - name: Install Dependencies @@ -33,13 +38,14 @@ jobs: echo "TGZ=devcontainers-cli-${VERSION}.tgz" | tee -a $GITHUB_ENV echo "TGZ_UPLOAD=devcontainers-cli-${VERSION}-${GITHUB_SHA:0:8}.tgz" | tee -a $GITHUB_ENV - name: Store TGZ - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v7 with: name: ${{ env.TGZ_UPLOAD }} path: ${{ env.TGZ }} tests-matrix: name: Tests Matrix runs-on: ubuntu-latest + timeout-minutes: 30 strategy: fail-fast: false matrix: @@ -62,13 +68,27 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://npm.pkg.github.com' scope: '@microsoft' + - name: Disable containerd image store + run: | + # Workaround for https://github.com/moby/moby/issues/52050 + DAEMON_JSON="/etc/docker/daemon.json" + if [ -f "$DAEMON_JSON" ]; then + sudo jq '. + {"features": {"containerd-snapshotter": false}}' "$DAEMON_JSON" \ + | sudo tee "${DAEMON_JSON}.tmp" > /dev/null + sudo mv "${DAEMON_JSON}.tmp" "$DAEMON_JSON" + else + echo '{"features": {"containerd-snapshotter": false}}' \ + | sudo tee "$DAEMON_JSON" > /dev/null + fi + cat "$DAEMON_JSON" + sudo systemctl restart docker - name: Tools Info run: | docker info @@ -93,13 +113,14 @@ jobs: # TODO: This should be expanded to run on different platforms # Most notably to test platform-specific credential helper behavior runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://npm.pkg.github.com' scope: '@microsoft' - name: Install Dependencies @@ -120,9 +141,22 @@ jobs: FEATURES_TEST__AZURE_REGISTRY_SCOPED_CREDENTIAL: ${{ secrets.FEATURES_TEST__AZURE_REGISTRY_SCOPED_CREDENTIAL }} + install-script: + name: Install Script + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + steps: + - uses: actions/checkout@v6 + - name: Run install.sh tests + run: sh scripts/install.test.sh + tests: name: Tests - needs: [tests-matrix, features-registry-compatibility] + needs: [tests-matrix, features-registry-compatibility, install-script] runs-on: ubuntu-latest steps: - name: Done diff --git a/.github/workflows/publish-dev-containers.yml b/.github/workflows/publish-dev-containers.yml index 931aa725e..7dae6ca8d 100644 --- a/.github/workflows/publish-dev-containers.yml +++ b/.github/workflows/publish-dev-containers.yml @@ -5,16 +5,20 @@ on: tags: - 'v*' +permissions: + contents: read + actions: read + jobs: main: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://registry.npmjs.org' scope: '@devcontainers' - name: Verify Versions @@ -33,7 +37,7 @@ jobs: echo "TGZ=devcontainers-cli-${VERSION}.tgz" | tee -a $GITHUB_ENV echo "TGZ_UPLOAD=devcontainers-cli-${VERSION}-${GITHUB_SHA:0:8}.tgz" | tee -a $GITHUB_ENV - name: Download TGZ - uses: dawidd6/action-download-artifact@ac66b43f0e6a346234dd65d4d0c8fbb31cb316e5 # v11 + uses: dawidd6/action-download-artifact@8305c0f1062bb0d184d09ef4493ecb9288447732 # v20 with: workflow: dev-containers.yml workflow_conclusion: success diff --git a/.github/workflows/test-docker-v20.yml b/.github/workflows/test-docker-v20.yml new file mode 100644 index 000000000..69f8d3569 --- /dev/null +++ b/.github/workflows/test-docker-v20.yml @@ -0,0 +1,64 @@ +name: Docker v20 Tests for dockerfile frontend test + +on: + push: + branches: ['main', 'directive-syntax-further-changes'] + pull_request: + branches: ['main'] + +permissions: + contents: read + +jobs: + test-docker-v20: + name: Docker v20.10 Compatibility + runs-on: ubuntu-22.04 + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v5 + with: + node-version: '20.x' + + - name: Install Docker v20.10 + run: | + sudo apt-get remove -y docker-ce docker-ce-cli containerd.io || true + sudo apt-get update + sudo apt-get install -y \ + ca-certificates \ + curl \ + gnupg \ + lsb-release + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + # Pin buildx < 0.31.0 to avoid API version 1.52 incompatibility with Docker 20.10 (max API 1.41) + # See https://github.com/docker/buildx/issues/3654 + BUILDX_VERSION=$(apt-cache madison docker-buildx-plugin | awk '{print $3}' | grep -v '^0\.3[1-9]\.' | head -1) + echo "Installing docker-buildx-plugin=$BUILDX_VERSION" + # Pin compose plugin to v2.x to avoid API incompatibility with Docker 20.10 + COMPOSE_VERSION=$(apt-cache madison docker-compose-plugin | awk '{print $3}' | grep '^2\.' | head -1) + echo "Installing docker-compose-plugin=$COMPOSE_VERSION" + sudo apt-get install -y docker-ce=5:20.10.* docker-ce-cli=5:20.10.* containerd.io docker-buildx-plugin="$BUILDX_VERSION" docker-compose-plugin="$COMPOSE_VERSION" + sudo systemctl restart docker + + - name: Verify Docker version, Install and Test + run: | + # Verify + docker version + DOCKER_VERSION=$(docker version --format '{{.Server.Version}}') + if [[ ! "$DOCKER_VERSION" =~ ^20\.10\. ]]; then + echo "ERROR: Expected Docker v20.10.x but got $DOCKER_VERSION" + exit 1 + fi + yarn install --frozen-lockfile + yarn type-check + yarn package + yarn test-matrix --forbid-only src/test/cli.up.test.ts + env: + CI: true \ No newline at end of file diff --git a/.github/workflows/test-docker-v29.yml b/.github/workflows/test-docker-v29.yml index 4560e0694..de475f246 100644 --- a/.github/workflows/test-docker-v29.yml +++ b/.github/workflows/test-docker-v29.yml @@ -6,17 +6,21 @@ on: pull_request: branches: ['main'] +permissions: + contents: read + jobs: test-docker-v29: name: Docker v29.0.0 Compatibility runs-on: ubuntu-latest + timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' - name: Install Docker v29.0.0 run: | diff --git a/.github/workflows/test-plan-item-validator.yml b/.github/workflows/test-plan-item-validator.yml index 22f75ba44..9c9739b6f 100644 --- a/.github/workflows/test-plan-item-validator.yml +++ b/.github/workflows/test-plan-item-validator.yml @@ -3,13 +3,17 @@ on: issues: types: [edited] +permissions: + contents: read + issues: write + jobs: main: runs-on: ubuntu-latest steps: - name: Checkout Actions if: contains(github.event.issue.labels.*.name, 'testplan-item') || contains(github.event.issue.labels.*.name, 'invalid-testplan-item') - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: 'microsoft/vscode-github-triage-actions' ref: stable diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 0fe0fcda8..5ac9743d1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -11,10 +11,15 @@ on: pull_request: branches: - '**' + +permissions: + contents: read + jobs: tests-matrix: name: Tests Matrix (Windows) runs-on: windows-latest + timeout-minutes: 15 strategy: fail-fast: false matrix: @@ -46,11 +51,11 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v5 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://npm.pkg.github.com' scope: '@microsoft' - name: Install Dependencies diff --git a/.mocharc.yml b/.mocharc.yml new file mode 100644 index 000000000..42bdad050 --- /dev/null +++ b/.mocharc.yml @@ -0,0 +1 @@ +timeout: 360000 # 6 minutes global safety net; individual suites override via this.timeout() diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8b4c1e5ef..baea00e62 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ "dbaeumer.vscode-eslint", + "hbenl.vscode-mocha-test-adapter" ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index a5ac98e49..cd5223cb5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,13 +3,13 @@ "search.exclude": { "dist": true }, - "typescript.tsc.autoDetect": "off", + "js/ts.tsc.autoDetect": "off", "eslint.options": { "rulePaths": [ "./build/eslint" ] }, - "mochaExplorer.files": "test/**/*.test.ts", + "mochaExplorer.files": "src/test/**/*.test.ts", "mochaExplorer.require": "ts-node/register", "mochaExplorer.env": { "TS_NODE_PROJECT": "src/test/tsconfig.json" @@ -17,12 +17,22 @@ "files.associations": { "devcontainer-features.json": "jsonc" }, - "typescript.tsdk": "node_modules/typescript/lib", + "js/ts.tsdk.path": "node_modules/typescript/lib", "git.branchProtection": [ "main", "release/*" ], "editor.formatOnSave": true, "editor.formatOnSaveMode": "modifications", - "editor.insertSpaces": false + "editor.insertSpaces": false, + "[json]": { + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.defaultFormatter": "vscode.json-language-features" + }, + "[jsonc]": { + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.defaultFormatter": "vscode.json-language-features" + } } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 5751ccb15..66792dd19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,67 @@ Notable changes. +## May 2026 + +### [0.87.0] +- Graduate lockfile from experimental to stable: lockfiles are now generated by default on `build` and `up`. (https://github.com/devcontainers/cli/issues/1195) + - New `--no-lockfile` flag to opt out of lockfile generation. + - New `--frozen-lockfile` flag to ensure the lockfile exists and remains unchanged. + - `--experimental-lockfile` and `--experimental-frozen-lockfile` are deprecated (still accepted with a warning). + +### [0.86.1] +- Do not write features supplied via `--additional-features` to the lockfile. (https://github.com/microsoft/vscode-remote-release/issues/11616) + +## April 2026 + +### [0.86.0] +- Bump basic-ftp from 5.2.0 to 5.2.2. (https://github.com/devcontainers/cli/pull/1201) +- Always write devcontainer.metadata label as JSON array. (https://github.com/devcontainers/cli/pull/1199) +- Normalize drive letter to lowercase on Windows to match VSCode. (https://github.com/devcontainers/cli/pull/1183) + +## March 2026 + +### [0.85.0] +- Inline buildx global build and target platform envvars when resolving base image and user. (https://github.com/devcontainers/cli/pull/1169) + +### [0.84.1] +- Bump tar from 7.5.10 to 7.5.11 due to [CVE-2026-31802](https://github.com/advisories/GHSA-9ppj-qmqm-q256). (https://github.com/devcontainers/cli/pull/1174) + +### [0.84.0] +- Dependencies update. (https://github.com/devcontainers/cli/pull/1167) + +## February 2026 + +### [0.83.3] +- Bump tar from 7.5.7 to 7.5.8. (https://github.com/devcontainers/cli/pull/1160) + +### [0.83.2] +- Improved logging for image inspect errors. (https://github.com/devcontainers/cli/pull/1152) + +### [0.83.1] +- Bump tar from 7.5.6 to 7.5.7. (https://github.com/devcontainers/cli/pull/1140) + +### [0.83.0] +- Add install script. (https://github.com/devcontainers/cli/pull/1142) +- Remove request body limit. (https://github.com/devcontainers/cli/pull/1141) +- Add BUILDKIT_INLINE_CACHE for container Feature path. (https://github.com/devcontainers/cli/pull/1135) + +## January 2026 + +### [0.82.0] +- devcontainer commands now use current directory as default workspace folder when not specified (https://github.com/devcontainers/cli/pull/1104) + +### [0.81.1] +- Update js-yaml and glob dependencies. (https://github.com/devcontainers/cli/pull/1128) + +### [0.81.0] +- Add option to mount a worktree's common folder. (https://github.com/devcontainers/cli/pull/1127) + +## December 2025 + +### [0.80.3] +- Fix: Skip download and injection of `dockerfile:1.4` syntax for Docker Engine versions [>=23.0.0](https://docs.docker.com/engine/release-notes/23.0/#2300)) - `dockerfile:1.4` or a subsequent version is already used by the docker engine package. (https://github.com/devcontainers/cli/pull/1113) + ## November 2025 ### [0.80.2] diff --git a/README.md b/README.md index d59523f83..39b043ba0 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,53 @@ This CLI is in active development. Current status: - [x] `devcontainer run-user-commands` - Runs lifecycle commands like `postCreateCommand` - [x] `devcontainer read-configuration` - Outputs current configuration for workspace - [x] `devcontainer exec` - Executes a command in a container with `userEnvProbe`, `remoteUser`, `remoteEnv`, and other properties applied +- [x] `devcontainer outdated` - Show outdated lockfile features +- [x] `devcontainer upgrade` - Upgrade lockfile features - [x] `devcontainer features <...>` - Tools to assist in authoring and testing [Dev Container Features](https://containers.dev/implementors/features/) - [x] `devcontainer templates <...>` - Tools to assist in authoring and testing [Dev Container Templates](https://containers.dev/implementors/templates/) - [ ] `devcontainer stop` - Stops containers - [ ] `devcontainer down` - Stops and deletes containers +Lockfiles (`.devcontainer-lock.json`) are generated by default when running `build` or `up` to pin feature versions for reproducible builds. Use `--no-lockfile` to opt out, or `--frozen-lockfile` to enforce an existing lockfile. + ## Try it out -We'd love for you to try out the dev container CLI and let us know what you think. You can quickly try it out in just a few simple steps, either by installing its npm package or building the CLI repo from sources (see "[Build from sources](#build-from-sources)"). +We'd love for you to try out the dev container CLI and let us know what you think. You can quickly try it out in just a few simple steps, either by using the install script, installing its npm package, or building the CLI repo from sources (see "[Build from sources](#build-from-sources)"). -To install the npm package you will need Python and C/C++ installed to build one of the dependencies (see, e.g., [here](https://github.com/microsoft/vscode/wiki/How-to-Contribute) for instructions). +### Install script + +You can install the CLI with a standalone script that downloads a bundled Node.js runtime, so no pre-installed Node.js is required. It works on Linux and macOS (x64 and arm64): + +```bash +curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh +``` + +Then add the install location to your PATH: + +```bash +export PATH="$HOME/.devcontainers/bin:$PATH" +``` + +You can also specify a version, a custom install directory, or update/uninstall an existing installation: + +```bash +# Install a specific version +sh install.sh --version 0.82.0 + +# Install to a custom directory +sh install.sh --prefix ~/.local/devcontainers + +# Update to latest +sh install.sh --update + +# Uninstall +sh install.sh --uninstall +``` ### npm install +To install the npm package you will need Python and C/C++ installed to build one of the dependencies (see, e.g., [here](https://github.com/microsoft/vscode/wiki/How-to-Contribute) for instructions). + ```bash npm install -g @devcontainers/cli ``` diff --git a/build/eslint/code-no-unexternalized-strings.js b/build/eslint/code-no-unexternalized-strings.js deleted file mode 100644 index 28fce5b9a..000000000 --- a/build/eslint/code-no-unexternalized-strings.js +++ /dev/null @@ -1,111 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -function isStringLiteral(node) { - return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string'; -} -function isDoubleQuoted(node) { - return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; -} -module.exports = new (_a = class NoUnexternalizedStrings { - constructor() { - this.meta = { - messages: { - doubleQuoted: 'Only use double-quoted strings for externalized strings.', - badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', - duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', - badMessage: 'Message argument to \'{{message}}\' must be a string literal.' - } - }; - } - create(context) { - const externalizedStringLiterals = new Map(); - const doubleQuotedStringLiterals = new Set(); - function collectDoubleQuotedStrings(node) { - if (isStringLiteral(node) && isDoubleQuoted(node)) { - doubleQuotedStringLiterals.add(node); - } - } - function visitLocalizeCall(node) { - // localize(key, message) - const [keyNode, messageNode] = node.arguments; - // (1) - // extract key so that it can be checked later - let key; - if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider - key = keyNode.value; - } - else if (keyNode.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression) { - for (let property of keyNode.properties) { - if (property.type === experimental_utils_1.AST_NODE_TYPES.Property && !property.computed) { - if (property.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier && property.key.name === 'key') { - if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider - key = property.value.value; - break; - } - } - } - } - } - if (typeof key === 'string') { - let array = externalizedStringLiterals.get(key); - if (!array) { - array = []; - externalizedStringLiterals.set(key, array); - } - array.push({ call: node, message: messageNode }); - } - // (2) - // remove message-argument from doubleQuoted list and make - // sure it is a string-literal - doubleQuotedStringLiterals.delete(messageNode); - if (!isStringLiteral(messageNode)) { - context.report({ - loc: messageNode.loc, - messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } - }); - } - } - function reportBadStringsAndBadKeys() { - // (1) - // report all strings that are in double quotes - for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); - } - for (const [key, values] of externalizedStringLiterals) { - // (2) - // report all invalid NLS keys - if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { - for (let value of values) { - context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); - } - } - // (2) - // report all invalid duplicates (same key, different message) - if (values.length > 1) { - for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { - context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); - } - } - } - } - } - return { - ['Literal']: (node) => collectDoubleQuotedStrings(node), - ['ExpressionStatement[directive] Literal:exit']: (node) => doubleQuotedStringLiterals.delete(node), - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node) => visitLocalizeCall(node), - ['Program:exit']: reportBadStringsAndBadKeys, - }; - } - }, - _a._rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/, - _a); diff --git a/build/eslint/code-no-unexternalized-strings.ts b/build/eslint/code-no-unexternalized-strings.ts deleted file mode 100644 index 29db884cd..000000000 --- a/build/eslint/code-no-unexternalized-strings.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; - -function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral { - return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string'; -} - -function isDoubleQuoted(node: TSESTree.StringLiteral): boolean { - return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"'; -} - -export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule { - - private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/; - - readonly meta: eslint.Rule.RuleMetaData = { - messages: { - doubleQuoted: 'Only use double-quoted strings for externalized strings.', - badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.', - duplicateKey: 'Duplicate key \'{{key}}\' with different message value.', - badMessage: 'Message argument to \'{{message}}\' must be a string literal.' - } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - - const externalizedStringLiterals = new Map(); - const doubleQuotedStringLiterals = new Set(); - - function collectDoubleQuotedStrings(node: TSESTree.Literal) { - if (isStringLiteral(node) && isDoubleQuoted(node)) { - doubleQuotedStringLiterals.add(node); - } - } - - function visitLocalizeCall(node: TSESTree.CallExpression) { - - // localize(key, message) - const [keyNode, messageNode] = (node).arguments; - - // (1) - // extract key so that it can be checked later - let key: string | undefined; - if (isStringLiteral(keyNode)) { - doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider - key = keyNode.value; - - } else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) { - for (let property of keyNode.properties) { - if (property.type === AST_NODE_TYPES.Property && !property.computed) { - if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') { - if (isStringLiteral(property.value)) { - doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider - key = property.value.value; - break; - } - } - } - } - } - if (typeof key === 'string') { - let array = externalizedStringLiterals.get(key); - if (!array) { - array = []; - externalizedStringLiterals.set(key, array); - } - array.push({ call: node, message: messageNode }); - } - - // (2) - // remove message-argument from doubleQuoted list and make - // sure it is a string-literal - doubleQuotedStringLiterals.delete(messageNode); - if (!isStringLiteral(messageNode)) { - context.report({ - loc: messageNode.loc, - messageId: 'badMessage', - data: { message: context.getSourceCode().getText(node) } - }); - } - } - - function reportBadStringsAndBadKeys() { - // (1) - // report all strings that are in double quotes - for (const node of doubleQuotedStringLiterals) { - context.report({ loc: node.loc, messageId: 'doubleQuoted' }); - } - - for (const [key, values] of externalizedStringLiterals) { - - // (2) - // report all invalid NLS keys - if (!key.match(NoUnexternalizedStrings._rNlsKeys)) { - for (let value of values) { - context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } }); - } - } - - // (2) - // report all invalid duplicates (same key, different message) - if (values.length > 1) { - for (let i = 1; i < values.length; i++) { - if (context.getSourceCode().getText(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) { - context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } }); - } - } - } - } - } - - return { - ['Literal']: (node: any) => collectDoubleQuotedStrings(node), - ['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node), - ['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node), - ['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node), - ['Program:exit']: reportBadStringsAndBadKeys, - }; - } -}; - diff --git a/build/eslint/code-no-unused-expressions.js b/build/eslint/code-no-unused-expressions.js deleted file mode 100644 index 80ae9a757..000000000 --- a/build/eslint/code-no-unused-expressions.js +++ /dev/null @@ -1,141 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js -// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642 - -/** - * @fileoverview Flag expressions in statement position that do not side effect - * @author Michael Ficarra - */ - -'use strict'; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -module.exports = { - meta: { - type: 'suggestion', - - docs: { - description: 'disallow unused expressions', - category: 'Best Practices', - recommended: false, - url: 'https://eslint.org/docs/rules/no-unused-expressions' - }, - - schema: [ - { - type: 'object', - properties: { - allowShortCircuit: { - type: 'boolean', - default: false - }, - allowTernary: { - type: 'boolean', - default: false - }, - allowTaggedTemplates: { - type: 'boolean', - default: false - } - }, - additionalProperties: false - } - ] - }, - - create(context) { - const config = context.options[0] || {}, - allowShortCircuit = config.allowShortCircuit || false, - allowTernary = config.allowTernary || false, - allowTaggedTemplates = config.allowTaggedTemplates || false; - - /** - * @param {ASTNode} node any node - * @returns {boolean} whether the given node structurally represents a directive - */ - function looksLikeDirective(node) { - return node.type === 'ExpressionStatement' && - node.expression.type === 'Literal' && typeof node.expression.value === 'string'; - } - - /** - * @param {Function} predicate ([a] -> Boolean) the function used to make the determination - * @param {a[]} list the input list - * @returns {a[]} the leading sequence of members in the given list that pass the given predicate - */ - function takeWhile(predicate, list) { - for (let i = 0; i < list.length; ++i) { - if (!predicate(list[i])) { - return list.slice(0, i); - } - } - return list.slice(); - } - - /** - * @param {ASTNode} node a Program or BlockStatement node - * @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body - */ - function directives(node) { - return takeWhile(looksLikeDirective, node.body); - } - - /** - * @param {ASTNode} node any node - * @param {ASTNode[]} ancestors the given node's ancestors - * @returns {boolean} whether the given node is considered a directive in its current position - */ - function isDirective(node, ancestors) { - const parent = ancestors[ancestors.length - 1], - grandparent = ancestors[ancestors.length - 2]; - - return (parent.type === 'Program' || parent.type === 'BlockStatement' && - (/Function/u.test(grandparent.type))) && - directives(parent).indexOf(node) >= 0; - } - - /** - * Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags. - * @param {ASTNode} node any node - * @returns {boolean} whether the given node is a valid expression - */ - function isValidExpression(node) { - if (allowTernary) { - - // Recursive check for ternary and logical expressions - if (node.type === 'ConditionalExpression') { - return isValidExpression(node.consequent) && isValidExpression(node.alternate); - } - } - - if (allowShortCircuit) { - if (node.type === 'LogicalExpression') { - return isValidExpression(node.right); - } - } - - if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') { - return true; - } - - return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await)Expression$/u.test(node.type) || - (node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0); - } - - return { - ExpressionStatement(node) { - if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) { - context.report({ node, message: 'Expected an assignment or function call and instead saw an expression.' }); - } - } - }; - - } -}; diff --git a/build/eslint/utils.js b/build/eslint/utils.js deleted file mode 100644 index c58e4e24b..000000000 --- a/build/eslint/utils.js +++ /dev/null @@ -1,37 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.createImportRuleListener = void 0; -function createImportRuleListener(validateImport) { - function _checkImport(node) { - if (node && node.type === 'Literal' && typeof node.value === 'string') { - validateImport(node, node.value); - } - } - return { - // import ??? from 'module' - ImportDeclaration: (node) => { - _checkImport(node.source); - }, - // import('module').then(...) OR await import('module') - ['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node) => { - _checkImport(node); - }, - // import foo = ... - ['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node) => { - _checkImport(node); - }, - // export ?? from 'module' - ExportAllDeclaration: (node) => { - _checkImport(node.source); - }, - // export {foo} from 'module' - ExportNamedDeclaration: (node) => { - _checkImport(node.source); - }, - }; -} -exports.createImportRuleListener = createImportRuleListener; diff --git a/build/eslint/utils.ts b/build/eslint/utils.ts deleted file mode 100644 index 428832e9c..000000000 --- a/build/eslint/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree } from '@typescript-eslint/experimental-utils'; - -export function createImportRuleListener(validateImport: (node: TSESTree.Literal, value: string) => any): eslint.Rule.RuleListener { - - function _checkImport(node: TSESTree.Node | null) { - if (node && node.type === 'Literal' && typeof node.value === 'string') { - validateImport(node, node.value); - } - } - - return { - // import ??? from 'module' - ImportDeclaration: (node: any) => { - _checkImport((node).source); - }, - // import('module').then(...) OR await import('module') - ['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node: any) => { - _checkImport(node); - }, - // import foo = ... - ['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node: any) => { - _checkImport(node); - }, - // export ?? from 'module' - ExportAllDeclaration: (node: any) => { - _checkImport((node).source); - }, - // export {foo} from 'module' - ExportNamedDeclaration: (node: any) => { - _checkImport((node).source); - }, - - }; -} diff --git a/build/eslint/vscode-dts-create-func.js b/build/eslint/vscode-dts-create-func.js deleted file mode 100644 index 5a27bf51c..000000000 --- a/build/eslint/vscode-dts-create-func.js +++ /dev/null @@ -1,35 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -module.exports = new class ApiLiteralOrTypes { - constructor() { - this.meta = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, - messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', } - }; - } - create(context) { - return { - ['TSDeclareFunction Identifier[name=/create.*/]']: (node) => { - var _a; - const decl = node.parent; - if (((_a = decl.returnType) === null || _a === void 0 ? void 0 : _a.typeAnnotation.type) !== experimental_utils_1.AST_NODE_TYPES.TSTypeReference) { - return; - } - if (decl.returnType.typeAnnotation.typeName.type !== experimental_utils_1.AST_NODE_TYPES.Identifier) { - return; - } - const ident = decl.returnType.typeAnnotation.typeName.name; - if (ident === 'Promise' || ident === 'Thenable') { - context.report({ - node, - messageId: 'sync' - }); - } - } - }; - } -}; diff --git a/build/eslint/vscode-dts-create-func.ts b/build/eslint/vscode-dts-create-func.ts deleted file mode 100644 index 295d099da..000000000 --- a/build/eslint/vscode-dts-create-func.ts +++ /dev/null @@ -1,40 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; - -export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { - - readonly meta: eslint.Rule.RuleMetaData = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' }, - messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - - return { - ['TSDeclareFunction Identifier[name=/create.*/]']: (node: any) => { - - const decl = (node).parent; - - if (decl.returnType?.typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) { - return; - } - if (decl.returnType.typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier) { - return; - } - - const ident = decl.returnType.typeAnnotation.typeName.name; - if (ident === 'Promise' || ident === 'Thenable') { - context.report({ - node, - messageId: 'sync' - }); - } - } - }; - } -}; diff --git a/build/eslint/vscode-dts-event-naming.js b/build/eslint/vscode-dts-event-naming.js deleted file mode 100644 index c93c18183..000000000 --- a/build/eslint/vscode-dts-event-naming.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -const experimental_utils_1 = require("@typescript-eslint/experimental-utils"); -module.exports = new (_a = class ApiEventNaming { - constructor() { - this.meta = { - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming' - }, - messages: { - naming: 'Event names must follow this patten: `on[Did|Will]`', - verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration', - subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API', - unknown: 'UNKNOWN event declaration, lint-rule needs tweaking' - } - }; - } - create(context) { - const config = context.options[0]; - const allowed = new Set(config.allowed); - const verbs = new Set(config.verbs); - return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node) => { - var _a, _b; - const def = (_b = (_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent) === null || _b === void 0 ? void 0 : _b.parent; - let ident; - if ((def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.Identifier) { - ident = def; - } - else if (((def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.TSPropertySignature || (def === null || def === void 0 ? void 0 : def.type) === experimental_utils_1.AST_NODE_TYPES.ClassProperty) && def.key.type === experimental_utils_1.AST_NODE_TYPES.Identifier) { - ident = def.key; - } - if (!ident) { - // event on unknown structure... - return context.report({ - node, - message: 'unknown' - }); - } - if (allowed.has(ident.name)) { - // configured exception - return; - } - const match = ApiEventNaming._nameRegExp.exec(ident.name); - if (!match) { - context.report({ - node: ident, - messageId: 'naming' - }); - return; - } - // check that is spelled out (configured) as verb - if (!verbs.has(match[2].toLowerCase())) { - context.report({ - node: ident, - messageId: 'verb', - data: { verb: match[2] } - }); - } - // check that a subject (if present) has occurred - if (match[3]) { - const regex = new RegExp(match[3], 'ig'); - const parts = context.getSourceCode().getText().split(regex); - if (parts.length < 3) { - context.report({ - node: ident, - messageId: 'subject', - data: { subject: match[3] } - }); - } - } - } - }; - } - }, - _a._nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/, - _a); diff --git a/build/eslint/vscode-dts-event-naming.ts b/build/eslint/vscode-dts-event-naming.ts deleted file mode 100644 index 6543c4586..000000000 --- a/build/eslint/vscode-dts-event-naming.ts +++ /dev/null @@ -1,91 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; - -export = new class ApiEventNaming implements eslint.Rule.RuleModule { - - private static _nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/; - - readonly meta: eslint.Rule.RuleMetaData = { - docs: { - url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming' - }, - messages: { - naming: 'Event names must follow this patten: `on[Did|Will]`', - verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration', - subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API', - unknown: 'UNKNOWN event declaration, lint-rule needs tweaking' - } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - - const config = <{ allowed: string[], verbs: string[] }>context.options[0]; - const allowed = new Set(config.allowed); - const verbs = new Set(config.verbs); - - return { - ['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: any) => { - - const def = (node).parent?.parent?.parent; - let ident: TSESTree.Identifier | undefined; - - if (def?.type === AST_NODE_TYPES.Identifier) { - ident = def; - - } else if ((def?.type === AST_NODE_TYPES.TSPropertySignature || def?.type === AST_NODE_TYPES.ClassProperty) && def.key.type === AST_NODE_TYPES.Identifier) { - ident = def.key; - } - - if (!ident) { - // event on unknown structure... - return context.report({ - node, - message: 'unknown' - }); - } - - if (allowed.has(ident.name)) { - // configured exception - return; - } - - const match = ApiEventNaming._nameRegExp.exec(ident.name); - if (!match) { - context.report({ - node: ident, - messageId: 'naming' - }); - return; - } - - // check that is spelled out (configured) as verb - if (!verbs.has(match[2].toLowerCase())) { - context.report({ - node: ident, - messageId: 'verb', - data: { verb: match[2] } - }); - } - - // check that a subject (if present) has occurred - if (match[3]) { - const regex = new RegExp(match[3], 'ig'); - const parts = context.getSourceCode().getText().split(regex); - if (parts.length < 3) { - context.report({ - node: ident, - messageId: 'subject', - data: { subject: match[3] } - }); - } - } - } - }; - } -}; - diff --git a/build/eslint/vscode-dts-interface-naming.js b/build/eslint/vscode-dts-interface-naming.js deleted file mode 100644 index 70ca81082..000000000 --- a/build/eslint/vscode-dts-interface-naming.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -var _a; -module.exports = new (_a = class ApiInterfaceNaming { - constructor() { - this.meta = { - messages: { - naming: 'Interfaces must not be prefixed with uppercase `I`', - } - }; - } - create(context) { - return { - ['TSInterfaceDeclaration Identifier']: (node) => { - const name = node.name; - if (ApiInterfaceNaming._nameRegExp.test(name)) { - context.report({ - node, - messageId: 'naming' - }); - } - } - }; - } - }, - _a._nameRegExp = /I[A-Z]/, - _a); diff --git a/build/eslint/vscode-dts-interface-naming.ts b/build/eslint/vscode-dts-interface-naming.ts deleted file mode 100644 index d9ec4e8c3..000000000 --- a/build/eslint/vscode-dts-interface-naming.ts +++ /dev/null @@ -1,35 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; -import { TSESTree } from '@typescript-eslint/experimental-utils'; - -export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule { - - private static _nameRegExp = /I[A-Z]/; - - readonly meta: eslint.Rule.RuleMetaData = { - messages: { - naming: 'Interfaces must not be prefixed with uppercase `I`', - } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - - return { - ['TSInterfaceDeclaration Identifier']: (node: any) => { - - const name = (node).name; - if (ApiInterfaceNaming._nameRegExp.test(name)) { - context.report({ - node, - messageId: 'naming' - }); - } - } - }; - } -}; - diff --git a/build/eslint/vscode-dts-literal-or-types.js b/build/eslint/vscode-dts-literal-or-types.js deleted file mode 100644 index 02e6de876..000000000 --- a/build/eslint/vscode-dts-literal-or-types.js +++ /dev/null @@ -1,23 +0,0 @@ -"use strict"; -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = new class ApiLiteralOrTypes { - constructor() { - this.meta = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, - messages: { useEnum: 'Use enums, not literal-or-types', } - }; - } - create(context) { - return { - ['TSTypeAnnotation TSUnionType TSLiteralType']: (node) => { - context.report({ - node: node, - messageId: 'useEnum' - }); - } - }; - } -}; diff --git a/build/eslint/vscode-dts-literal-or-types.ts b/build/eslint/vscode-dts-literal-or-types.ts deleted file mode 100644 index 01a3eb215..000000000 --- a/build/eslint/vscode-dts-literal-or-types.ts +++ /dev/null @@ -1,25 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as eslint from 'eslint'; - -export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule { - - readonly meta: eslint.Rule.RuleMetaData = { - docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' }, - messages: { useEnum: 'Use enums, not literal-or-types', } - }; - - create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { - return { - ['TSTypeAnnotation TSUnionType TSLiteralType']: (node: any) => { - context.report({ - node: node, - messageId: 'useEnum' - }); - } - }; - } -}; diff --git a/build/hygiene.js b/build/hygiene.js index e3326f555..4959e988e 100644 --- a/build/hygiene.js +++ b/build/hygiene.js @@ -5,15 +5,30 @@ 'use strict'; -const filter = require('gulp-filter'); const es = require('event-stream'); const tsfmt = require('typescript-formatter'); -const gulpeslint = require('gulp-eslint'); +const { ESLint } = require('eslint'); const VinylFile = require('vinyl'); const vfs = require('vinyl-fs'); const path = require('path'); const fs = require('fs'); -const pall = require('p-all'); +const pall = require('p-all').default; +const { minimatch } = require('minimatch'); + +function fileFilter(patterns) { + return es.through(function (file) { + const rel = file.relative; + const match = patterns.every(p => { + if (p.startsWith('!')) { + return !minimatch(rel, p.slice(1), { dot: true }); + } + return true; + }) && patterns.some(p => !p.startsWith('!') && minimatch(rel, p, { dot: true })); + if (match) { + this.emit('data', file); + } + }); +} /** * Hygiene works by creating cascading subsets of all our files and @@ -175,21 +190,26 @@ function hygiene(some) { } const result = input - .pipe(filter(f => !f.stat.isDirectory())) - .pipe(filter(indentationFilter)) + .pipe(es.through(function (f) { if (!f.stat.isDirectory()) this.emit('data', f); })) + .pipe(fileFilter(indentationFilter)) .pipe(indentation) - .pipe(filter(copyrightFilter)) + .pipe(fileFilter(copyrightFilter)) .pipe(copyrights) - .pipe(filter(tsHygieneFilter)) + .pipe(fileFilter(tsHygieneFilter)) .pipe(formatting) - .pipe(gulpeslint({ - configFile: '.eslintrc.js', - rulePaths: ['./build/eslint'] - })) - .pipe(gulpeslint.formatEach('compact')) - .pipe(gulpeslint.result(result => { - errorCount += result.warningCount; - errorCount += result.errorCount; + .pipe(es.map(function (file, cb) { + const eslint = new ESLint(); + eslint.lintText(file.contents.toString('utf8'), { + filePath: file.path, + }).then(results => { + for (const result of results) { + errorCount += result.warningCount + result.errorCount; + for (const message of result.messages) { + console.error(`${file.relative}:${message.line}:${message.column}: ${message.message} [${message.ruleId}]`); + } + } + cb(null, file); + }).catch(err => cb(err)); })); let count = 0; diff --git a/docs/contributing-code.md b/docs/contributing-code.md new file mode 100644 index 000000000..abfde9c5c --- /dev/null +++ b/docs/contributing-code.md @@ -0,0 +1,176 @@ +# Contributing Code + +This guide covers everything you need to set up a development environment, build, test, and submit code changes to the Dev Containers CLI. For the proposal and specification process, see [CONTRIBUTING.md](../CONTRIBUTING.md). + +## Prerequisites + +- [Node.js](https://nodejs.org/) >= 20 +- [Docker](https://www.docker.com/) (required for running integration tests — they create real containers) +- [Git](https://git-scm.com/) +- [yarn](https://yarnpkg.com/) (used for dependency installation) + +## Setting up your development environment + +Fork and clone the repository: + +```sh +git clone https://github.com//cli.git +cd cli +``` + +### Option A: Dev Container (recommended) + +The repository includes a [dev container configuration](../.devcontainer/devcontainer.json) that provides a ready-to-go environment with Node.js, TypeScript, and Docker-in-Docker pre-configured. + +1. Open the cloned repository in VS Code. +2. When prompted, select **Reopen in Container** (requires the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)). Alternatively, open the repository in [GitHub Codespaces](https://github.com/features/codespaces). +3. The `postCreateCommand` automatically runs `yarn install` to install all dependencies. + +You are ready to build and test. + +### Option B: Local setup + +1. Install Node.js >= 20 and Docker. +2. Install dependencies: + + ```sh + yarn install + ``` + + Ensure Docker is running — it is needed for the integration test suite. + + Some tests build containers for non-native architectures (e.g., `linux/arm64` on an x64 host, or vice versa). To run these locally, register QEMU emulators: + + ```sh + docker run --privileged --rm tonistiigi/binfmt --install all + ``` + + This is needed once per boot (or per WSL session on Windows). On macOS with Docker Desktop, cross-architecture emulation is built in and this step is not required. + +3. *(Optional)* Install [Podman](https://podman.io/) if you want to run the Podman-specific tests. The CLI supports both Docker and Podman as container engines, and the test suite includes a separate set of tests (`cli.podman.test.ts`) that verify Podman compatibility using `--docker-path podman`. These tests will fail with `spawn podman ENOENT` if Podman is not installed — this is expected and does not indicate a code problem. The CI GitHub workflow runs these tests on `ubuntu-latest` where Podman is pre-installed. + +## Project structure + +The CLI is written in TypeScript and organized as multiple sub-projects using [TypeScript project references](https://www.typescriptlang.org/docs/handbook/project-references.html): + +| Sub-project | Path | Purpose | +| --- | --- | --- | +| `spec-common` | `src/spec-common/` | Shared utilities (async helpers, CLI host, process management, shell server) | +| `spec-configuration` | `src/spec-configuration/` | Configuration parsing, OCI registry interactions, Features/Templates configuration | +| `spec-node` | `src/spec-node/` | Core CLI logic — container lifecycle, Docker/Compose integration, Feature utilities | +| `spec-shutdown` | `src/spec-shutdown/` | Docker CLI wrapper utilities (container inspection, execution, lifecycle management) | +| `spec-utils` | `src/spec-utils/` | General utilities (logging, HTTP requests, filesystem helpers) | + +Key files: + +- `devcontainer.js` — Entry point that loads the bundled CLI from `dist/spec-node/devContainersSpecCLI.js`. +- `esbuild.js` — Build script that bundles the TypeScript output with esbuild. +- `src/test/` — Test files and fixture configurations under `src/test/configs/`. + +## Development workflow + +### 1. Build + +Start the dev build watchers — run these in separate terminals (or use the [VS Code build task](#vs-code-integration)): + +```sh +npm run watch # incremental esbuild (rebuilds on save) +npm run type-check-watch # tsc in watch mode (reports type errors) +``` + +For a one-shot build instead, run `npm run compile`. To remove all build output, run `npm run clean`. + +### 2. Run + +After building, invoke the CLI directly: + +```sh +node devcontainer.js --help +node devcontainer.js up --workspace-folder +node devcontainer.js build --workspace-folder +node devcontainer.js run-user-commands --workspace-folder +``` + +### 3. Test + +Tests use [Mocha](https://mochajs.org/) and [Chai](https://www.chaijs.com/) and require Docker because they create and tear down real containers. + +Before running tests, package the CLI into a tarball: + +```sh +npm run package +``` + +Tests install the CLI from the generated `devcontainers-cli-.tgz` and shell out to it as a subprocess. You must re-run `npm run package` after any code change so that the tarball reflects your latest changes. Running `npm run compile` alone is **not** sufficient — it builds the JavaScript output but does not create the tarball that the tests depend on. + +```sh +npm test # all tests +npm run test-container-features # Features tests only +npm run test-container-templates # Templates tests only +``` + +#### Adding tests + +- Place new test files in `src/test/` with a `.test.ts` suffix. +- Place test fixture `devcontainer.json` configurations under `src/test/configs//`. +- Use the helpers in `src/test/testUtils.ts` (`shellExec`, `devContainerUp`, `devContainerDown`) for container lifecycle management in tests. + +### 4. Validate and submit + +Before committing, run the same checks CI runs: + +```sh +npm run type-check # full type-check +npm run package # production build (minified) + pack into .tgz +npm run precommit # lint, formatting, copyright headers +npm test # full test suite (may take a very long time to run, consider running a subset of tests during development) +``` + +Then push your branch and open a pull request against `main`. Link any related [repo issues](https://github.com/devcontainers/cli/issues) or [specification issues](https://github.com/microsoft/dev-container-spec/issues) in the PR description. + +## VS Code integration + +The repository includes VS Code configuration in `.vscode/` for building, debugging, and testing. + +### Build task + +The default build task (**Ctrl+Shift+B** / **Cmd+Shift+B**) is **Build Dev Containers CLI**. It runs `npm run watch` and `npm run type-check-watch` in parallel so you get both bundled output and type errors as you edit. + +### Debug configurations + +Two launch configurations are provided in `.vscode/launch.json`: + +- **Launch CLI - up** — Runs the CLI's `up` command against `src/test/configs/example/`. Edit the `args` array to point at a different config or subcommand. +- **Launch Tests** — Runs the full Mocha test suite under the debugger. + +### Editor settings + +The workspace recommends the [ESLint extension](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) for inline lint feedback. The workspace settings (`.vscode/settings.json`) configure format-on-save, tab indentation, and the workspace TypeScript SDK. + +## Troubleshooting + +### Docker not available + +Tests will fail if Docker is not running. Make sure the Docker daemon is started. If using the dev container, Docker-in-Docker is configured automatically. + +### `node-pty` native module build failures + +The `node-pty` dependency includes native code. If you see build errors during `yarn install`, ensure you have the required build tools for your platform (e.g., `build-essential` on Debian/Ubuntu, Xcode Command Line Tools on macOS). + +### Leftover test containers + +If tests are interrupted, containers may be left running. Single-container tests label their containers with `devcontainer.local_folder`: + +```sh +docker rm -f $(docker ps -aq --filter "label=devcontainer.local_folder") +``` + +Compose-based tests also create sidecar containers (e.g., `db` services) that don't carry that label. To remove those, filter by the compose config path: + +```sh +docker rm -f $(docker ps -a --format '{{.ID}} {{.Label "com.docker.compose.project.config_files"}}' | grep src/test/configs | awk '{print $1}') +``` + +### Podman test failures + +If you don't have Podman installed, `cli.podman.test.ts` will fail with `spawn podman ENOENT`. This is safe to ignore — CI will run them. See [Local setup](#option-b-local-setup) for details on installing Podman or skipping these tests. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 000000000..e44a85adf --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,61 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import typescriptParser from '@typescript-eslint/parser'; +import typescriptPlugin from '@typescript-eslint/eslint-plugin'; +import stylisticPlugin from '@stylistic/eslint-plugin'; + +export default [ + { + ignores: ['**/node_modules/**'], + }, + { + files: ['src/**/*.ts'], + languageOptions: { + parser: typescriptParser, + sourceType: 'module', + }, + plugins: { + '@typescript-eslint': typescriptPlugin, + '@stylistic': stylisticPlugin, + }, + rules: { + '@stylistic/member-delimiter-style': [ + 'warn', + { + multiline: { + delimiter: 'semi', + requireLast: true, + }, + singleline: { + delimiter: 'semi', + requireLast: false, + }, + }, + ], + 'semi': ['warn', 'always'], + 'constructor-super': 'warn', + 'curly': 'warn', + 'eqeqeq': ['warn', 'always'], + 'no-async-promise-executor': 'warn', + 'no-buffer-constructor': 'warn', + 'no-caller': 'warn', + 'no-debugger': 'warn', + 'no-duplicate-case': 'warn', + 'no-duplicate-imports': 'warn', + 'no-eval': 'warn', + 'no-extra-semi': 'warn', + 'no-new-wrappers': 'warn', + 'no-redeclare': 'off', + 'no-sparse-arrays': 'warn', + 'no-throw-literal': 'warn', + 'no-unsafe-finally': 'warn', + 'no-unused-labels': 'warn', + '@typescript-eslint/no-redeclare': 'warn', + 'no-var': 'warn', + 'no-unused-expressions': ['warn', { allowTernary: true }], + }, + }, +]; diff --git a/package.json b/package.json index abbde6d76..b62f6402e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@devcontainers/cli", "description": "Dev Containers CLI", - "version": "0.80.2", + "version": "0.87.0", "bin": { "devcontainer": "devcontainer.js" }, @@ -15,7 +15,7 @@ }, "license": "MIT", "engines": { - "node": "^16.13.0 || >=18.0.0" + "node": ">=20.0.0" }, "scripts": { "compile": "npm-run-all clean-dist compile-dev", @@ -32,13 +32,13 @@ "tsc-b": "tsc -b", "tsc-b-w": "tsc -b -w", "precommit": "node build/hygiene.js", - "lint": "eslint -c .eslintrc.js --rulesdir ./build/eslint --max-warnings 0 --ext .ts ./src", + "lint": "eslint --max-warnings 0 ./src", "npm-pack": "npm pack", "clean": "npm-run-all clean-dist clean-built", "clean-dist": "rimraf dist", "clean-built": "rimraf built", "test": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/*.test.ts", - "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit", + "test-matrix": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit --retries 1", "test-container-features": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/*.test.ts", "test-container-features-cli": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-features/featuresCLICommands.test.ts", "test-container-templates": "env TS_NODE_PROJECT=src/test/tsconfig.json mocha -r ts-node/register --exit src/test/container-templates/*.test.ts" @@ -54,57 +54,56 @@ "scripts/updateUID.Dockerfile" ], "devDependencies": { - "@stylistic/eslint-plugin": "^3.0.1", + "@stylistic/eslint-plugin": "^5.10.0", "@types/chai": "^4.3.20", - "@types/chalk": "^2.2.4", "@types/follow-redirects": "^1.14.4", "@types/js-yaml": "^4.0.9", "@types/mocha": "^10.0.10", "@types/ncp": "^2.0.8", - "@types/node": "^18.19.127", + "@types/node": "^20.19.37", "@types/pull-stream": "^3.6.7", "@types/recursive-readdir": "^2.2.4", - "@types/semver": "^7.5.8", + "@types/semver": "^7.7.1", "@types/shell-quote": "^1.7.5", - "@types/tar": "^6.1.13", "@types/text-table": "^0.2.5", - "@types/yargs": "^17.0.33", - "@typescript-eslint/eslint-plugin": "^8.26.0", - "@typescript-eslint/experimental-utils": "^5.62.0", - "@typescript-eslint/parser": "^8.26.0", + "@types/yargs": "^17.0.35", + "@typescript-eslint/eslint-plugin": "^8.56.1", + "@typescript-eslint/parser": "^8.56.1", "chai": "^4.5.0", "copyfiles": "^2.4.1", - "esbuild": "^0.25.0", - "eslint": "^8.57.0", + "esbuild": "^0.27.3", + "eslint": "^10.0.2", "event-stream": "^4.0.1", - "gulp-eslint": "^6.0.0", - "gulp-filter": "^9.0.1", - "mocha": "^11.1.0", + "minimatch": "^10.2.4", + "mocha": "^11.7.5", "npm-run-all": "^4.1.5", - "p-all": "^5.0.0", - "rimraf": "^5.0.10", + "p-all": "^5.0.1", + "rimraf": "^6.1.3", "ts-node": "^10.9.2", - "typescript": "^5.8.2", + "typescript": "^5.9.3", "typescript-formatter": "^7.2.2", - "vinyl": "^3.0.0", - "vinyl-fs": "^4.0.0" + "vinyl": "^3.0.1", + "vinyl-fs": "^4.0.2" }, "dependencies": { - "chalk": "^5.4.1", - "follow-redirects": "^1.15.9", - "js-yaml": "^4.1.0", + "chalk": "^5.6.2", + "follow-redirects": "^1.15.11", + "js-yaml": "^4.1.1", "jsonc-parser": "^3.3.1", "ncp": "^2.0.0", - "node-pty": "^1.0.0", + "node-pty": "~1.0.0", "proxy-agent": "^6.5.0", "pull-stream": "^3.7.0", "recursive-readdir": "^2.2.3", - "semver": "^7.7.1", - "shell-quote": "^1.8.2", + "semver": "^7.7.4", + "shell-quote": "^1.8.3", "stream-to-pull-stream": "^1.7.3", - "tar": "^6.2.1", + "tar": "^7.5.10", "text-table": "^0.2.0", "vscode-uri": "^3.1.0", "yargs": "~17.7.2" + }, + "resolutions": { + "serialize-javascript": "^7.0.5" } } diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 000000000..e44a36712 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,672 @@ +#!/bin/sh +# install.sh - Install @devcontainers/cli with bundled Node.js +# +# Usage: +# curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh +# wget -qO- https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh +# +# Options: +# --prefix DIR Installation directory (default: ~/.devcontainers) +# --version VER Dev Containers CLI version to install (default: latest) +# --node-version VER Node.js major version (default: 20) +# --update Update existing installation to latest versions +# --uninstall Remove the installation +# --help Show this help message +# +# Environment: +# DEVCONTAINERS_INSTALL_DIR Override default installation directory + +set -e + +# Default configuration +INSTALL_PREFIX="${DEVCONTAINERS_INSTALL_DIR:-$HOME/.devcontainers}" +CLI_VERSION="latest" +NODE_MAJOR_VERSION="20" +UPDATE_MODE=false +UNINSTALL_MODE=false + +# Terminal colors (disabled if not a tty) +setup_colors() { + if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[0;33m' + BLUE='\033[0;34m' + BOLD='\033[1m' + RESET='\033[0m' + else + RED='' + GREEN='' + YELLOW='' + BLUE='' + BOLD='' + RESET='' + fi +} + +say() { + printf '%b\n' "${GREEN}>${RESET} $1" +} + +warn() { + printf '%b\n' "${YELLOW}warning${RESET}: $1" >&2 +} + +error() { + printf '%b\n' "${RED}error${RESET}: $1" >&2 +} + +# Print usage information +usage() { + cat << 'EOF' +Install the Dev Containers CLI with bundled Node.js + +Usage: + curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh + sh install.sh [OPTIONS] + +Options: + --prefix DIR Installation directory (default: ~/.devcontainers) + --version VER Dev Containers CLI version to install (default: latest) + --node-version VER Node.js major version (default: 20) + --update Update existing installation to latest versions + --uninstall Remove the installation + --help Show this help message + +Environment: + DEVCONTAINERS_INSTALL_DIR Override default installation directory + +Examples: + # Install latest version + curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh + + # Install specific version + sh install.sh --version 0.82.0 + + # Install to custom directory + sh install.sh --prefix ~/.local/devcontainers + + # Update existing installation + sh install.sh --update + + # Uninstall + sh install.sh --uninstall + +After installation, add to your shell profile: + export PATH="$HOME/.devcontainers/bin:$PATH" +EOF +} + +# Parse command-line arguments +parse_args() { + while [ $# -gt 0 ]; do + case "$1" in + --prefix) + INSTALL_PREFIX="$2" + shift 2 + ;; + --prefix=*) + INSTALL_PREFIX="${1#*=}" + shift + ;; + --version) + CLI_VERSION="$2" + shift 2 + ;; + --version=*) + CLI_VERSION="${1#*=}" + shift + ;; + --node-version) + NODE_MAJOR_VERSION="$2" + shift 2 + ;; + --node-version=*) + NODE_MAJOR_VERSION="${1#*=}" + shift + ;; + --update) + UPDATE_MODE=true + shift + ;; + --uninstall) + UNINSTALL_MODE=true + shift + ;; + --help|-h) + usage + exit 0 + ;; + *) + error "Unknown option: $1" + usage + exit 1 + ;; + esac + done +} + +# Detect platform (OS and architecture) +detect_platform() { + # OS detection + case "$(uname -s)" in + Linux*) + PLATFORM="linux" + ;; + Darwin*) + PLATFORM="darwin" + ;; + CYGWIN*|MINGW*|MSYS*) + error "Windows is not supported by this installer." + error "Please use WSL (Windows Subsystem for Linux) or install via npm:" + error " npm install -g @devcontainers/cli" + exit 1 + ;; + *) + error "Unsupported operating system: $(uname -s)" + exit 1 + ;; + esac + + # Architecture detection + case "$(uname -m)" in + x86_64|amd64) + ARCH="x64" + ;; + aarch64|arm64) + ARCH="arm64" + ;; + armv7l|armv6l) + error "32-bit ARM is not supported." + exit 1 + ;; + *) + error "Unsupported architecture: $(uname -m)" + exit 1 + ;; + esac + + # macOS: Detect if running under Rosetta 2 and prefer native arm64 + if [ "$PLATFORM" = "darwin" ] && [ "$ARCH" = "x64" ]; then + if sysctl -n sysctl.proc_translated 2>/dev/null | grep -q 1; then + say "Detected Rosetta 2 translation, using native arm64 binary" + ARCH="arm64" + fi + fi +} + +# Check for required tools +check_prerequisites() { + # Check for curl or wget + if command -v curl >/dev/null 2>&1; then + DOWNLOADER="curl" + elif command -v wget >/dev/null 2>&1; then + DOWNLOADER="wget" + else + error "Either 'curl' or 'wget' is required but neither was found." + exit 1 + fi + + # Check for tar + if ! command -v tar >/dev/null 2>&1; then + error "'tar' is required but not found." + exit 1 + fi + + # Check if we can write to the install directory + if [ -e "$INSTALL_PREFIX" ]; then + if [ ! -d "$INSTALL_PREFIX" ]; then + error "Installation path exists but is not a directory: $INSTALL_PREFIX" + exit 1 + fi + if [ ! -w "$INSTALL_PREFIX" ]; then + error "No write permission for installation directory: $INSTALL_PREFIX" + exit 1 + fi + else + # Check if we can create the directory + PARENT_DIR="$(dirname "$INSTALL_PREFIX")" + if [ ! -w "$PARENT_DIR" ]; then + error "No write permission to create installation directory: $INSTALL_PREFIX" + exit 1 + fi + fi +} + +# Download a file using curl or wget +download() { + url="$1" + output="$2" + + if [ "$DOWNLOADER" = "curl" ]; then + curl -fSL --retry 3 --retry-delay 2 -o "$output" "$url" + else + wget --tries=3 --waitretry=2 -q -O "$output" "$url" + fi +} + +# Fetch content from a URL (for API calls) +fetch() { + url="$1" + + if [ "$DOWNLOADER" = "curl" ]; then + curl -fsSL "$url" + else + wget -qO- "$url" + fi +} + +# Resolve "latest" CLI version from npm registry +resolve_cli_version() { + if [ "$CLI_VERSION" = "latest" ]; then + say "Resolving latest Dev Containers CLI version..." + version=$(fetch "https://registry.npmjs.org/@devcontainers/cli/latest" | \ + sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1) + if [ -z "$version" ]; then + error "Failed to resolve latest Dev Containers CLI version from npm registry" + exit 1 + fi + CLI_VERSION="$version" + fi + say "Dev Containers CLI version: $CLI_VERSION" +} + +# Resolve full Node.js version from major version +resolve_node_version() { + say "Resolving Node.js v$NODE_MAJOR_VERSION LTS version..." + + # Get the latest version for the major version + index_url="https://nodejs.org/dist/index.json" + version=$(fetch "$index_url" | \ + sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"v\('"$NODE_MAJOR_VERSION"'\.[^"]*\)".*/\1/p' | head -1) + + if [ -z "$version" ]; then + error "Failed to resolve Node.js v$NODE_MAJOR_VERSION version" + exit 1 + fi + + NODE_VERSION="$version" + say "Node.js version: v$NODE_VERSION" +} + +# Get Node.js download URL +get_node_url() { + # Prefer .tar.xz if available, fall back to .tar.gz + echo "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${PLATFORM}-${ARCH}.tar.xz" +} + +# Get CLI download URL from npm registry +get_cli_url() { + echo "https://registry.npmjs.org/@devcontainers/cli/-/cli-${CLI_VERSION}.tgz" +} + +# Install Node.js +install_node() { + node_dir="$INSTALL_PREFIX/node" + version_dir="$node_dir/v$NODE_VERSION" + + # Check if already installed + if [ -d "$version_dir" ] && [ -x "$version_dir/bin/node" ]; then + say "Node.js v$NODE_VERSION is already installed" + else + say "Downloading Node.js v$NODE_VERSION..." + + tmp_dir=$(mktemp -d) + trap 'rm -rf "$tmp_dir"' EXIT + + node_url=$(get_node_url) + tarball="$tmp_dir/node.tar.xz" + + if ! download "$node_url" "$tarball"; then + # Try .tar.gz if .tar.xz failed + node_url="${node_url%.xz}.gz" + tarball="$tmp_dir/node.tar.gz" + say "Trying .tar.gz format..." + download "$node_url" "$tarball" + fi + + say "Extracting Node.js..." + mkdir -p "$node_dir" + + # Extract to temp first, then move + extract_dir="$tmp_dir/extracted" + mkdir -p "$extract_dir" + + case "$tarball" in + *.xz) + # Try xz decompression + if command -v xz >/dev/null 2>&1; then + xz -d -c "$tarball" | tar -xf - -C "$extract_dir" + else + # Some tar implementations support -J for xz + tar -xJf "$tarball" -C "$extract_dir" 2>/dev/null || { + error "xz decompression not available. Please install xz-utils." + exit 1 + } + fi + ;; + *.gz) + tar -xzf "$tarball" -C "$extract_dir" + ;; + esac + + # Move extracted directory to version directory + mv "$extract_dir"/node-v*/* "$extract_dir"/ + rmdir "$extract_dir"/node-v* 2>/dev/null || true + mkdir -p "$version_dir" + mv "$extract_dir"/* "$version_dir"/ + + trap - EXIT + rm -rf "$tmp_dir" + fi + + # Update current symlink + say "Activating Node.js v$NODE_VERSION..." + ln -sfn "v$NODE_VERSION" "$node_dir/current" + + # Save metadata + mkdir -p "$INSTALL_PREFIX/.metadata" + echo "$NODE_VERSION" > "$INSTALL_PREFIX/.metadata/node-version" +} + +# Install CLI +install_cli() { + cli_dir="$INSTALL_PREFIX/cli" + version_dir="$cli_dir/$CLI_VERSION" + + # Check if already installed + if [ -d "$version_dir/package" ] && [ -f "$version_dir/package/devcontainer.js" ]; then + say "Dev Containers CLI v$CLI_VERSION is already installed" + else + say "Downloading Dev Containers CLI v$CLI_VERSION..." + + tmp_dir=$(mktemp -d) + trap 'rm -rf "$tmp_dir"' EXIT + + cli_url=$(get_cli_url) + tarball="$tmp_dir/cli.tgz" + + download "$cli_url" "$tarball" + + say "Extracting Dev Containers CLI..." + mkdir -p "$version_dir" + tar -xzf "$tarball" -C "$version_dir" + + trap - EXIT + rm -rf "$tmp_dir" + fi + + # Update current symlink + say "Activating Dev Containers CLI v$CLI_VERSION..." + ln -sfn "$CLI_VERSION" "$cli_dir/current" + + # Save metadata + mkdir -p "$INSTALL_PREFIX/.metadata" + echo "$CLI_VERSION" > "$INSTALL_PREFIX/.metadata/installed-version" +} + +# Create wrapper script +create_wrapper() { + bin_dir="$INSTALL_PREFIX/bin" + wrapper="$bin_dir/devcontainer" + + say "Creating wrapper script..." + mkdir -p "$bin_dir" + + cat > "$wrapper" << 'WRAPPER_EOF' +#!/bin/sh +# Dev Containers CLI wrapper - generated by install.sh +# https://github.com/devcontainers/cli + +set -e + +# Resolve the installation directory +# Handle both direct execution and symlinked scenarios +if [ -L "$0" ]; then + # Follow symlink + SCRIPT_PATH="$(readlink "$0" 2>/dev/null || readlink -f "$0" 2>/dev/null || echo "$0")" +else + SCRIPT_PATH="$0" +fi + +# Get absolute path to script directory +SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_PATH")" && pwd)" +INSTALL_DIR="$(dirname "$SCRIPT_DIR")" + +# Paths to bundled components +NODE_BIN="$INSTALL_DIR/node/current/bin/node" +CLI_ENTRY="$INSTALL_DIR/cli/current/package/devcontainer.js" + +# Verify Node.js exists +if [ ! -x "$NODE_BIN" ]; then + echo "Error: Node.js not found at $NODE_BIN" >&2 + echo "Installation may be corrupted. Please reinstall:" >&2 + echo " curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh" >&2 + exit 1 +fi + +# Verify CLI exists +if [ ! -f "$CLI_ENTRY" ]; then + echo "Error: Dev Containers CLI not found at $CLI_ENTRY" >&2 + echo "Installation may be corrupted. Please reinstall:" >&2 + echo " curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh" >&2 + exit 1 +fi + +# Execute the CLI with bundled Node.js +exec "$NODE_BIN" "$CLI_ENTRY" "$@" +WRAPPER_EOF + + chmod +x "$wrapper" +} + +# Verify installation +verify_installation() { + say "Verifying installation..." + + node_bin="$INSTALL_PREFIX/node/current/bin/node" + cli_entry="$INSTALL_PREFIX/cli/current/package/devcontainer.js" + wrapper="$INSTALL_PREFIX/bin/devcontainer" + + if [ ! -x "$node_bin" ]; then + error "Node.js binary not found or not executable" + exit 1 + fi + + if [ ! -f "$cli_entry" ]; then + error "Dev Containers CLI entry point not found" + exit 1 + fi + + if [ ! -x "$wrapper" ]; then + error "Wrapper script not found or not executable" + exit 1 + fi + + # Try to get version + version=$("$wrapper" --version 2>/dev/null || true) + if [ -n "$version" ]; then + say "Installed: devcontainer $version" + else + warn "Could not verify Dev Containers CLI version, but files are in place" + fi +} + +# Check for existing installation and warn about conflicts +check_existing() { + # Check for existing devcontainer in PATH + existing=$(command -v devcontainer 2>/dev/null || true) + if [ -n "$existing" ]; then + # Check if it's our installation + case "$existing" in + "$INSTALL_PREFIX"*) + # It's our installation, that's fine + ;; + *) + warn "Found existing devcontainer at: $existing" + warn "After installation, ensure $INSTALL_PREFIX/bin is first in your PATH" + ;; + esac + fi + + # Check for existing installation directory + if [ -d "$INSTALL_PREFIX" ] && [ ! "$UPDATE_MODE" = true ]; then + if [ -f "$INSTALL_PREFIX/.metadata/installed-version" ]; then + current_version=$(cat "$INSTALL_PREFIX/.metadata/installed-version") + say "Found existing installation: v$current_version" + say "Use --update to update, or --uninstall to remove first" + fi + fi +} + +# Update existing installation +do_update() { + if [ ! -d "$INSTALL_PREFIX" ] || [ ! -f "$INSTALL_PREFIX/.metadata/installed-version" ]; then + error "No existing installation found at $INSTALL_PREFIX" + error "Run without --update to perform a fresh installation" + exit 1 + fi + + current_cli=$(cat "$INSTALL_PREFIX/.metadata/installed-version" 2>/dev/null || echo "unknown") + current_node=$(cat "$INSTALL_PREFIX/.metadata/node-version" 2>/dev/null || echo "unknown") + + say "Current installation:" + say " Dev Containers CLI: v$current_cli" + say " Node.js: v$current_node" + + # Resolve latest versions + CLI_VERSION="latest" + resolve_cli_version + resolve_node_version + + # Update components + if [ "$current_cli" = "$CLI_VERSION" ]; then + say "Dev Containers CLI is already up to date" + else + say "Updating Dev Containers CLI: v$current_cli -> v$CLI_VERSION" + install_cli + fi + + if [ "$current_node" = "$NODE_VERSION" ]; then + say "Node.js is already up to date" + else + say "Updating Node.js: v$current_node -> v$NODE_VERSION" + install_node + fi + + # Recreate wrapper in case it changed + create_wrapper + verify_installation +} + +# Uninstall +do_uninstall() { + if [ ! -d "$INSTALL_PREFIX" ]; then + say "Nothing to uninstall at $INSTALL_PREFIX" + exit 0 + fi + + say "Uninstalling from $INSTALL_PREFIX..." + rm -rf "$INSTALL_PREFIX" + say "Uninstallation complete" + say "" + say "Don't forget to remove the PATH entry from your shell profile:" + say " export PATH=\"$INSTALL_PREFIX/bin:\$PATH\"" +} + +# Print post-installation instructions +print_instructions() { + bin_path="$INSTALL_PREFIX/bin" + + echo "" + say "${BOLD}Installation complete!${RESET}" + echo "" + + # Check if already in PATH + case ":$PATH:" in + *":$bin_path:"*) + say "The installation directory is already in your PATH." + say "You can now use: devcontainer --help" + ;; + *) + say "Add the following to your shell profile to use devcontainer:" + echo "" + echo " export PATH=\"$bin_path:\$PATH\"" + echo "" + + # Detect shell and suggest profile file + shell_name=$(basename "${SHELL:-/bin/sh}") + case "$shell_name" in + bash) + if [ -f "$HOME/.bash_profile" ]; then + say "For bash, add to: ~/.bash_profile" + else + say "For bash, add to: ~/.bashrc" + fi + ;; + zsh) + say "For zsh, add to: ~/.zshrc" + ;; + fish) + say "For fish, run:" + echo " fish_add_path $bin_path" + ;; + *) + say "Add to your shell's profile file" + ;; + esac + echo "" + say "Then restart your shell or run:" + echo " export PATH=\"$bin_path:\$PATH\"" + ;; + esac + + echo "" + say "To update:" + echo " curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh -s -- --update" + say "To uninstall:" + echo " curl -fsSL https://raw.githubusercontent.com/devcontainers/cli/main/scripts/install.sh | sh -s -- --uninstall" + say "Or simply: rm -rf $INSTALL_PREFIX" +} + +# Main function +main() { + setup_colors + parse_args "$@" + + echo "" + say "${BOLD}Dev Containers CLI installer${RESET}" + echo "" + + # Handle uninstall + if [ "$UNINSTALL_MODE" = true ]; then + do_uninstall + exit 0 + fi + + detect_platform + say "Platform: $PLATFORM-$ARCH" + say "Install directory: $INSTALL_PREFIX" + + check_prerequisites + check_existing + + # Handle update + if [ "$UPDATE_MODE" = true ]; then + do_update + print_instructions + exit 0 + fi + + # Fresh installation + resolve_cli_version + resolve_node_version + + install_node + install_cli + create_wrapper + verify_installation + print_instructions +} + +main "$@" diff --git a/scripts/install.test.sh b/scripts/install.test.sh new file mode 100755 index 000000000..2a73b883e --- /dev/null +++ b/scripts/install.test.sh @@ -0,0 +1,580 @@ +#!/bin/sh +# install.test.sh - Tests for install.sh +# +# Usage: +# sh scripts/install.test.sh +# +# Can be run in CI or locally. Uses a temp directory for all installs. +# Requires network access to download Node.js and the CLI package. + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +INSTALL_SCRIPT="$SCRIPT_DIR/install.sh" + +# ── Test framework ──────────────────────────────────────────────── + +TESTS_RUN=0 +TESTS_PASSED=0 +TESTS_FAILED=0 +FAILED_NAMES="" + +# Colors (disabled in non-tty / CI) +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + C_RED='\033[0;31m' + C_GREEN='\033[0;32m' + C_YELLOW='\033[0;33m' + C_BOLD='\033[1m' + C_RESET='\033[0m' +else + C_RED='' + C_GREEN='' + C_YELLOW='' + C_BOLD='' + C_RESET='' +fi + +pass() { + TESTS_PASSED=$((TESTS_PASSED + 1)) + printf '%b\n' " ${C_GREEN}✓${C_RESET} $1" +} + +fail() { + TESTS_FAILED=$((TESTS_FAILED + 1)) + FAILED_NAMES="$FAILED_NAMES\n - $1" + printf '%b\n' " ${C_RED}✗${C_RESET} $1" + if [ -n "${2:-}" ]; then + printf ' %s\n' "$2" + fi +} + +assert_eq() { + expected="$1" + actual="$2" + msg="$3" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ "$expected" = "$actual" ]; then + pass "$msg" + else + fail "$msg" "expected: '$expected', got: '$actual'" + fi +} + +assert_contains() { + haystack="$1" + needle="$2" + msg="$3" + TESTS_RUN=$((TESTS_RUN + 1)) + case "$haystack" in + *"$needle"*) + pass "$msg" + ;; + *) + fail "$msg" "expected output to contain: '$needle'" + ;; + esac +} + +assert_file_exists() { + path="$1" + msg="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ -f "$path" ]; then + pass "$msg" + else + fail "$msg" "file not found: $path" + fi +} + +assert_dir_exists() { + path="$1" + msg="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ -d "$path" ]; then + pass "$msg" + else + fail "$msg" "directory not found: $path" + fi +} + +assert_executable() { + path="$1" + msg="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ -x "$path" ]; then + pass "$msg" + else + fail "$msg" "not executable: $path" + fi +} + +assert_symlink() { + path="$1" + msg="$2" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ -L "$path" ]; then + pass "$msg" + else + fail "$msg" "not a symlink: $path" + fi +} + +assert_exit_code() { + expected="$1" + actual="$2" + msg="$3" + TESTS_RUN=$((TESTS_RUN + 1)) + if [ "$expected" = "$actual" ]; then + pass "$msg" + else + fail "$msg" "expected exit code $expected, got $actual" + fi +} + +# ── Setup / teardown ───────────────────────────────────────────── + +TEST_TMPDIR="" +setup() { + TEST_TMPDIR="$(mktemp -d)" +} + +teardown() { + if [ -n "$TEST_TMPDIR" ] && [ -d "$TEST_TMPDIR" ]; then + rm -rf "$TEST_TMPDIR" + fi +} + +# ── Tests: --help ───────────────────────────────────────────────── + +test_help_flag() { + printf '%b\n' "${C_BOLD}--help flag${C_RESET}" + setup + + output=$(sh "$INSTALL_SCRIPT" --help 2>&1) || true + + assert_contains "$output" "Install the Dev Containers CLI" "--help shows description" + assert_contains "$output" "--prefix" "--help shows --prefix option" + assert_contains "$output" "--version" "--help shows --version option" + assert_contains "$output" "--node-version" "--help shows --node-version option" + assert_contains "$output" "--update" "--help shows --update option" + assert_contains "$output" "--uninstall" "--help shows --uninstall option" + assert_contains "$output" "DEVCONTAINERS_INSTALL_DIR" "--help shows env var" + + teardown +} + +test_help_short_flag() { + printf '%b\n' "${C_BOLD}-h flag${C_RESET}" + setup + + output=$(sh "$INSTALL_SCRIPT" -h 2>&1) || true + assert_contains "$output" "Install the Dev Containers CLI" "-h shows help" + + teardown +} + +# ── Tests: argument parsing errors ──────────────────────────────── + +test_unknown_option() { + printf '%b\n' "${C_BOLD}Unknown option${C_RESET}" + setup + + output=$(sh "$INSTALL_SCRIPT" --bogus 2>&1) && rc=0 || rc=$? + assert_exit_code "1" "$rc" "exits with code 1 on unknown option" + assert_contains "$output" "Unknown option" "reports unknown option" + + teardown +} + +# ── Tests: --uninstall on missing dir ───────────────────────────── + +test_uninstall_no_dir() { + printf '%b\n' "${C_BOLD}Uninstall with no existing installation${C_RESET}" + setup + + prefix="$TEST_TMPDIR/nonexistent" + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --uninstall 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "exits 0 when nothing to uninstall" + assert_contains "$output" "Nothing to uninstall" "reports nothing to uninstall" + + teardown +} + +# ── Tests: --update on missing installation ─────────────────────── + +test_update_no_installation() { + printf '%b\n' "${C_BOLD}Update with no existing installation${C_RESET}" + setup + + prefix="$TEST_TMPDIR/empty" + mkdir -p "$prefix" + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --update 2>&1) && rc=0 || rc=$? + assert_exit_code "1" "$rc" "exits 1 when no installation found" + assert_contains "$output" "No existing installation" "reports missing installation" + + teardown +} + +# ── Tests: DEVCONTAINERS_INSTALL_DIR env var ────────────────────── + +test_env_var_prefix() { + printf '%b\n' "${C_BOLD}DEVCONTAINERS_INSTALL_DIR env var${C_RESET}" + setup + + # The env var should be reflected in the help output or the + # install run. We just test that the script picks it up by + # running --uninstall (lightweight) against a nonexistent path. + prefix="$TEST_TMPDIR/from-env" + output=$(DEVCONTAINERS_INSTALL_DIR="$prefix" sh "$INSTALL_SCRIPT" --uninstall 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "exits 0 with env-var prefix" + assert_contains "$output" "Nothing to uninstall" "uses env-var prefix path" + + teardown +} + +# ── Tests: --prefix flag overrides env var ──────────────────────── + +test_prefix_overrides_env() { + printf '%b\n' "${C_BOLD}--prefix overrides DEVCONTAINERS_INSTALL_DIR${C_RESET}" + setup + + env_dir="$TEST_TMPDIR/env-dir" + flag_dir="$TEST_TMPDIR/flag-dir" + output=$(DEVCONTAINERS_INSTALL_DIR="$env_dir" sh "$INSTALL_SCRIPT" --prefix "$flag_dir" --uninstall 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "exits 0" + # The output should reference flag_dir, not env_dir + assert_contains "$output" "Nothing to uninstall" "--prefix is used over env var" + + teardown +} + +# ── Tests: full install with a specific version ─────────────────── + +test_full_install() { + printf '%b\n' "${C_BOLD}Full install (specific CLI version)${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + + # Use a known CLI version to make the test deterministic + cli_version="0.75.0" + + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "install exits 0" + + # Directory structure + assert_dir_exists "$prefix/bin" "bin/ directory created" + assert_dir_exists "$prefix/node" "node/ directory created" + assert_dir_exists "$prefix/cli" "cli/ directory created" + assert_dir_exists "$prefix/.metadata" ".metadata/ directory created" + + # Wrapper script + assert_file_exists "$prefix/bin/devcontainer" "wrapper script exists" + assert_executable "$prefix/bin/devcontainer" "wrapper script is executable" + + # Symlinks + assert_symlink "$prefix/node/current" "node/current is a symlink" + assert_symlink "$prefix/cli/current" "cli/current is a symlink" + + # Node.js binary + assert_executable "$prefix/node/current/bin/node" "node binary is executable" + + # CLI entry point + assert_file_exists "$prefix/cli/current/package/devcontainer.js" "CLI entry point exists" + + # Metadata + assert_file_exists "$prefix/.metadata/installed-version" "CLI version metadata written" + assert_file_exists "$prefix/.metadata/node-version" "Node version metadata written" + + installed_version=$(cat "$prefix/.metadata/installed-version") + assert_eq "$cli_version" "$installed_version" "metadata records correct CLI version" + + # Wrapper executes successfully + version_output=$("$prefix/bin/devcontainer" --version 2>/dev/null) && wrc=0 || wrc=$? + assert_exit_code "0" "$wrc" "wrapper --version exits 0" + assert_contains "$version_output" "$cli_version" "wrapper reports installed version" + + teardown +} + +# ── Tests: idempotent install ───────────────────────────────────── + +test_idempotent_install() { + printf '%b\n' "${C_BOLD}Idempotent install (run twice)${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + cli_version="0.75.0" + + # First install + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" >/dev/null 2>&1 + + # Second install – same version, should succeed and say "already installed" + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "second install exits 0" + assert_contains "$output" "already installed" "detects existing Node.js or CLI" + + # Still works + version_output=$("$prefix/bin/devcontainer" --version 2>/dev/null) && wrc=0 || wrc=$? + assert_exit_code "0" "$wrc" "wrapper still works after second install" + + teardown +} + +# ── Tests: uninstall after install ──────────────────────────────── + +test_uninstall_after_install() { + printf '%b\n' "${C_BOLD}Uninstall removes installation${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + cli_version="0.75.0" + + # Install + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" >/dev/null 2>&1 + + # Uninstall + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --uninstall 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "uninstall exits 0" + assert_contains "$output" "Uninstallation complete" "reports completion" + + # Directory should be gone + TESTS_RUN=$((TESTS_RUN + 1)) + if [ ! -d "$prefix" ]; then + pass "install directory removed" + else + fail "install directory removed" "directory still exists: $prefix" + fi + + teardown +} + +# ── Tests: update existing installation ─────────────────────────── + +test_update_existing() { + printf '%b\n' "${C_BOLD}Update existing installation${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + + # Install an older version first + old_version="0.72.0" + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$old_version" >/dev/null 2>&1 + + installed=$(cat "$prefix/.metadata/installed-version") + assert_eq "$old_version" "$installed" "initial version installed" + + # Update to a slightly newer specific version + new_version="0.75.0" + # Fake update by doing a fresh install with --version (--update resolves "latest") + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$new_version" >/dev/null 2>&1 + + updated=$(cat "$prefix/.metadata/installed-version") + assert_eq "$new_version" "$updated" "version updated in metadata" + + # Wrapper reports new version + version_output=$("$prefix/bin/devcontainer" --version 2>/dev/null) && wrc=0 || wrc=$? + assert_exit_code "0" "$wrc" "wrapper works after version change" + assert_contains "$version_output" "$new_version" "wrapper reports new version" + + teardown +} + +# ── Tests: wrapper handles missing node gracefully ──────────────── + +test_wrapper_missing_node() { + printf '%b\n' "${C_BOLD}Wrapper error when Node.js missing${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + cli_version="0.75.0" + + # Install + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" >/dev/null 2>&1 + + # Remove node binary + rm -rf "$prefix/node" + + output=$("$prefix/bin/devcontainer" --version 2>&1) && rc=0 || rc=$? + assert_exit_code "1" "$rc" "wrapper exits 1 when node missing" + assert_contains "$output" "Node.js not found" "wrapper reports missing Node.js" + + teardown +} + +# ── Tests: wrapper handles missing CLI gracefully ───────────────── + +test_wrapper_missing_cli() { + printf '%b\n' "${C_BOLD}Wrapper error when CLI missing${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + cli_version="0.75.0" + + # Install + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" >/dev/null 2>&1 + + # Remove CLI + rm -rf "$prefix/cli" + + output=$("$prefix/bin/devcontainer" --version 2>&1) && rc=0 || rc=$? + assert_exit_code "1" "$rc" "wrapper exits 1 when CLI missing" + assert_contains "$output" "Dev Containers CLI not found" "wrapper reports missing Dev Containers CLI" + + teardown +} + +# ── Tests: install via symlinked wrapper ────────────────────────── + +test_wrapper_via_symlink() { + printf '%b\n' "${C_BOLD}Wrapper works when invoked via symlink${C_RESET}" + setup + + prefix="$TEST_TMPDIR/devcontainers" + cli_version="0.75.0" + + sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" >/dev/null 2>&1 + + # Create a symlink to the wrapper in a different directory + link_dir="$TEST_TMPDIR/links" + mkdir -p "$link_dir" + ln -s "$prefix/bin/devcontainer" "$link_dir/devcontainer" + + version_output=$("$link_dir/devcontainer" --version 2>/dev/null) && wrc=0 || wrc=$? + assert_exit_code "0" "$wrc" "symlinked wrapper exits 0" + assert_contains "$version_output" "$cli_version" "symlinked wrapper reports version" + + teardown +} + +# ── Tests: install to path with spaces ──────────────────────────── + +test_path_with_spaces() { + printf '%b\n' "${C_BOLD}Install to path with spaces${C_RESET}" + setup + + prefix="$TEST_TMPDIR/my dev containers" + + cli_version="0.75.0" + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "$cli_version" 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "install to spaced path exits 0" + + assert_file_exists "$prefix/bin/devcontainer" "wrapper exists in spaced path" + + version_output=$("$prefix/bin/devcontainer" --version 2>/dev/null) && wrc=0 || wrc=$? + assert_exit_code "0" "$wrc" "wrapper works from spaced path" + assert_contains "$version_output" "$cli_version" "reports correct version from spaced path" + + teardown +} + +# ── Tests: non-writable prefix ──────────────────────────────────── + +test_non_writable_prefix() { + printf '%b\n' "${C_BOLD}Error on non-writable prefix${C_RESET}" + setup + + # Skip if running as root (root can write anywhere) + if [ "$(id -u)" = "0" ]; then + TESTS_RUN=$((TESTS_RUN + 1)) + pass "skipped (running as root)" + teardown + return + fi + + prefix="/usr/local/no-permission-test-devcontainers-$$" + output=$(sh "$INSTALL_SCRIPT" --prefix "$prefix" --version "0.75.0" 2>&1) && rc=0 || rc=$? + assert_exit_code "1" "$rc" "exits 1 for non-writable prefix" + assert_contains "$output" "No write permission" "reports permission error" + + teardown +} + +# ── Tests: --prefix= form (equals delimiter) ───────────────────── + +test_prefix_equals_form() { + printf '%b\n' "${C_BOLD}--prefix=DIR form${C_RESET}" + setup + + prefix="$TEST_TMPDIR/eq-form" + # Just verify parsing works – use uninstall for a lightweight check + output=$(sh "$INSTALL_SCRIPT" "--prefix=$prefix" --uninstall 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "--prefix=DIR is accepted" + assert_contains "$output" "Nothing to uninstall" "--prefix=DIR path is used" + + teardown +} + +test_version_equals_form() { + printf '%b\n' "${C_BOLD}--version=VER form${C_RESET}" + setup + + prefix="$TEST_TMPDIR/ver-eq" + output=$(sh "$INSTALL_SCRIPT" "--prefix=$prefix" "--version=0.75.0" 2>&1) && rc=0 || rc=$? + assert_exit_code "0" "$rc" "--version=VER install exits 0" + assert_contains "$output" "0.75.0" "version from --version=VER is used" + + teardown +} + +# ── Run all tests ───────────────────────────────────────────────── + +printf '%b\n' "" +printf '%b\n' "${C_BOLD}install.sh test suite${C_RESET}" +printf '%b\n' "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +printf '%b\n' "" + +# Fast tests (no network required) +test_help_flag +printf '\n' +test_help_short_flag +printf '\n' +test_unknown_option +printf '\n' +test_uninstall_no_dir +printf '\n' +test_update_no_installation +printf '\n' +test_env_var_prefix +printf '\n' +test_prefix_overrides_env +printf '\n' +test_prefix_equals_form +printf '\n' +test_non_writable_prefix +printf '\n' + +# Integration tests (require network, download Node.js + CLI) +printf '%b\n' "${C_YELLOW}Integration tests (requires network)${C_RESET}" +printf '\n' +test_full_install +printf '\n' +test_idempotent_install +printf '\n' +test_uninstall_after_install +printf '\n' +test_update_existing +printf '\n' +test_wrapper_missing_node +printf '\n' +test_wrapper_missing_cli +printf '\n' +test_wrapper_via_symlink +printf '\n' +test_path_with_spaces +printf '\n' +test_version_equals_form +printf '\n' + +# ── Summary ─────────────────────────────────────────────────────── + +printf '%b\n' "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" +if [ "$TESTS_FAILED" -eq 0 ]; then + printf '%b\n' "${C_GREEN}${C_BOLD}All $TESTS_RUN tests passed${C_RESET}" +else + printf '%b\n' "${C_RED}${C_BOLD}$TESTS_FAILED of $TESTS_RUN tests failed${C_RESET}" + printf '%b\n' "$FAILED_NAMES" +fi +printf '%b\n' "" + +exit "$TESTS_FAILED" diff --git a/src/spec-configuration/containerCollectionsOCI.ts b/src/spec-configuration/containerCollectionsOCI.ts index 082bb0daa..a2e4ad55f 100644 --- a/src/spec-configuration/containerCollectionsOCI.ts +++ b/src/spec-configuration/containerCollectionsOCI.ts @@ -563,8 +563,9 @@ export async function getBlob(params: CommonParams, url: string, ociCacheDir: st { file: tempTarballPath, cwd: destCachePath, - filter: (tPath: string, stat: tar.FileStat) => { - output.write(`Testing '${tPath}'(${stat.type})`, LogLevel.Trace); + filter: (tPath, stat) => { + const entryType = 'type' in stat ? stat.type : (stat.isFile() ? 'File' : stat.isDirectory() ? 'Directory' : 'Other'); + output.write(`Testing '${tPath}'(${entryType})`, LogLevel.Trace); const cleanedPath = tPath .replace(/\\/g, '/') .replace(/^\.\//, ''); @@ -574,7 +575,8 @@ export async function getBlob(params: CommonParams, url: string, ociCacheDir: st return false; // Skip } - if (stat.type.toString() === 'File') { + const isFile = 'type' in stat ? stat.type === 'File' : stat.isFile(); + if (isFile) { files.push(tPath); } @@ -594,7 +596,7 @@ export async function getBlob(params: CommonParams, url: string, ociCacheDir: st { file: tempTarballPath, cwd: ociCacheDir, - filter: (tPath: string, _: tar.FileStat) => { + filter: (tPath, _) => { return tPath === `./${metadataFile}`; } }); diff --git a/src/spec-configuration/containerFeaturesConfiguration.ts b/src/spec-configuration/containerFeaturesConfiguration.ts index 662ed3b65..5957d0896 100644 --- a/src/spec-configuration/containerFeaturesConfiguration.ts +++ b/src/spec-configuration/containerFeaturesConfiguration.ts @@ -10,6 +10,7 @@ import * as tar from 'tar'; import * as crypto from 'crypto'; import * as semver from 'semver'; import * as os from 'os'; +import * as fs from 'fs'; import { DevContainerConfig, DevContainerFeature, VSCodeCustomizations } from './configuration'; import { mkdirpLocal, readLocalFile, rmLocal, writeLocalFile, cpDirectoryLocal, isLocalFile } from '../spec-utils/pfs'; @@ -192,8 +193,8 @@ export interface ContainerFeatureInternalParams { env: NodeJS.ProcessEnv; skipFeatureAutoMapping: boolean; platform: NodeJS.Platform; - experimentalLockfile?: boolean; - experimentalFrozenLockfile?: boolean; + noLockfile?: boolean; + frozenLockfile?: boolean; } // TODO: Move to node layer. @@ -484,7 +485,7 @@ export async function generateFeaturesConfig(params: ContainerFeatureInternalPar const ociCacheDir = await prepareOCICache(dstFolder); - const { lockfile, initLockfile } = await readLockfile(config); + const { lockfile } = params.noLockfile ? { lockfile: undefined } : await readLockfile(config); const processFeature = async (_userFeature: DevContainerFeature) => { return await processFeatureIdentifier(params, configPath, workspaceRoot, _userFeature, lockfile); @@ -507,7 +508,9 @@ export async function generateFeaturesConfig(params: ContainerFeatureInternalPar await fetchFeatures(params, featuresConfig, dstFolder, ociCacheDir, lockfile); await logFeatureAdvisories(params, featuresConfig); - await writeLockfile(params, config, await generateLockfile(featuresConfig), initLockfile); + if (!params.noLockfile) { + await writeLockfile(params, config, await generateLockfile(featuresConfig, config, additionalFeatures)); + } return featuresConfig; } @@ -1097,7 +1100,7 @@ export async function fetchContentsAtTarballUri(params: { output: Log; env: Node } // Filter what gets emitted from the tar.extract(). - const filter = (file: string, _: tar.FileStat) => { + const filter = (file: string, _: fs.Stats | tar.ReadEntry) => { // Don't include .dotfiles or the archive itself. if (file.startsWith('./.') || file === `./${V1_ASSET_NAME}` || file === './.') { return false; @@ -1128,7 +1131,7 @@ export async function fetchContentsAtTarballUri(params: { output: Log; env: Node { file: tempTarballPath, cwd: featCachePath, - filter: (path: string, _: tar.FileStat) => { + filter: (path, _) => { return path === `./${metadataFile}`; } }); diff --git a/src/spec-configuration/lockfile.ts b/src/spec-configuration/lockfile.ts index 9de0ba0b2..e41ae2025 100644 --- a/src/spec-configuration/lockfile.ts +++ b/src/spec-configuration/lockfile.ts @@ -13,10 +13,15 @@ export interface Lockfile { features: Record; } -export async function generateLockfile(featuresConfig: FeaturesConfig): Promise { +export async function generateLockfile(featuresConfig: FeaturesConfig, config?: DevContainerConfig, additionalFeatures?: Record>): Promise { + // Features supplied only via `--additional-features` (i.e., not present in `config.features`) + // should not be written to the lockfile. + const configFeatureKeys = new Set(Object.keys(config?.features || {})); + const excludeUserFeatureIds = new Set(Object.keys(additionalFeatures || {}).filter(key => !configFeatureKeys.has(key))); return featuresConfig.featureSets .map(f => [f, f.sourceInformation] as const) .filter((tup): tup is [FeatureSet, OCISourceInformation | DirectTarballSourceInformation] => ['oci', 'direct-tarball'].indexOf(tup[1].type) !== -1) + .filter(([, source]) => !excludeUserFeatureIds.has(source.userFeatureId)) .map(([set, source]) => { const dependsOn = Object.keys(set.features[0].dependsOn || {}); return { @@ -40,7 +45,11 @@ export async function generateLockfile(featuresConfig: FeaturesConfig): Promise< }); } -export async function writeLockfile(params: ContainerFeatureInternalParams, config: DevContainerConfig, lockfile: Lockfile, forceInitLockfile?: boolean): Promise { +export async function writeLockfile(params: ContainerFeatureInternalParams, config: DevContainerConfig, lockfile: Lockfile): Promise { + if (params.noLockfile) { + return; + } + const lockfilePath = getLockfilePath(config); const oldLockfileContent = await readLocalFile(lockfilePath) .catch(err => { @@ -49,17 +58,25 @@ export async function writeLockfile(params: ContainerFeatureInternalParams, conf } }); - if (!forceInitLockfile && !oldLockfileContent && !params.experimentalLockfile && !params.experimentalFrozenLockfile) { - return; - } - - const newLockfileContentString = JSON.stringify(lockfile, null, 2); + // Trailing newline per POSIX convention + const newLockfileContentString = JSON.stringify(lockfile, null, 2) + '\n'; const newLockfileContent = Buffer.from(newLockfileContentString); - if (params.experimentalFrozenLockfile && !oldLockfileContent) { + if (params.frozenLockfile && !oldLockfileContent) { throw new Error('Lockfile does not exist.'); } - if (!oldLockfileContent || !newLockfileContent.equals(oldLockfileContent)) { - if (params.experimentalFrozenLockfile) { + // Normalize the existing lockfile through JSON.parse -> JSON.stringify to produce + // the same canonical format as newLockfileContentString, so that the string comparison + // below ignores cosmetic differences (indentation, trailing whitespace, etc.). + let oldLockfileNormalized: string | undefined; + if (oldLockfileContent) { + try { + oldLockfileNormalized = JSON.stringify(JSON.parse(oldLockfileContent.toString()), null, 2) + '\n'; + } catch { + // Empty or invalid JSON; treat as needing rewrite. + } + } + if (!oldLockfileNormalized || oldLockfileNormalized !== newLockfileContentString) { + if (params.frozenLockfile) { throw new Error('Lockfile does not match.'); } await writeLocalFile(lockfilePath, newLockfileContent); diff --git a/src/spec-configuration/typings/zlib-zstd.d.ts b/src/spec-configuration/typings/zlib-zstd.d.ts new file mode 100644 index 000000000..6614b3eab --- /dev/null +++ b/src/spec-configuration/typings/zlib-zstd.d.ts @@ -0,0 +1,6 @@ +// Stub types for Zstd compression classes added in Node.js 23.8.0 +// Required for minizlib's type definitions which reference these types +declare module 'zlib' { + interface ZstdCompress extends NodeJS.ReadWriteStream {} + interface ZstdDecompress extends NodeJS.ReadWriteStream {} +} diff --git a/src/spec-node/collectionCommonUtils/packageCommandImpl.ts b/src/spec-node/collectionCommonUtils/packageCommandImpl.ts index 271c0a049..cd54533c3 100644 --- a/src/spec-node/collectionCommonUtils/packageCommandImpl.ts +++ b/src/spec-node/collectionCommonUtils/packageCommandImpl.ts @@ -1,4 +1,4 @@ -import tar from 'tar'; +import * as tar from 'tar'; import * as jsonc from 'jsonc-parser'; import * as os from 'os'; import * as recursiveDirReader from 'recursive-readdir'; diff --git a/src/spec-node/configContainer.ts b/src/spec-node/configContainer.ts index ee6b93cb3..c0b21eb82 100644 --- a/src/spec-node/configContainer.ts +++ b/src/spec-node/configContainer.ts @@ -46,7 +46,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu ? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath) || (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined)) : overrideConfigFile; - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, output, workspaceMountConsistencyDefault, overrideConfigFile) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, params.mountGitWorktreeCommonDir, output, workspaceMountConsistencyDefault, overrideConfigFile) || undefined; if (!configs) { if (configPath || workspace) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configPath || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); @@ -60,7 +60,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu const { dockerCLI, dockerComposeCLI } = params; const { env } = common; - const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo }; + const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo }; await ensureNoDisallowedFeatures(cliParams, config, additionalFeatures, idLabels); await runInitializeCommand({ ...params, common: { ...common, output: common.lifecycleHook.output } }, config.initializeCommand, common.lifecycleHook.onDidInput); @@ -79,7 +79,7 @@ async function resolveWithLocalFolder(params: DockerResolverParameters, parsedAu return result; } -export async function readDevContainerConfigFile(cliHost: CLIHost, workspace: Workspace | undefined, configFile: URI, mountWorkspaceGitRoot: boolean, output: Log, consistency?: BindMountConsistency, overrideConfigFile?: URI) { +export async function readDevContainerConfigFile(cliHost: CLIHost, workspace: Workspace | undefined, configFile: URI, mountWorkspaceGitRoot: boolean, mountGitWorktreeCommonDir: boolean, output: Log, consistency?: BindMountConsistency, overrideConfigFile?: URI) { const documents = createDocuments(cliHost); const content = await documents.readDocument(overrideConfigFile ?? configFile); if (!content) { @@ -90,7 +90,7 @@ export async function readDevContainerConfigFile(cliHost: CLIHost, workspace: Wo if (!updated || typeof updated !== 'object' || Array.isArray(updated)) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile, cliHost.platform)}) must contain a JSON object literal.` }); } - const workspaceConfig = await getWorkspaceConfiguration(cliHost, workspace, updated, mountWorkspaceGitRoot, output, consistency); + const workspaceConfig = await getWorkspaceConfiguration(cliHost, workspace, updated, mountWorkspaceGitRoot, mountGitWorktreeCommonDir, output, consistency); const substitute0: SubstituteConfig = value => substitute({ platform: cliHost.platform, localWorkspaceFolder: workspace?.rootFolderPath, diff --git a/src/spec-node/containerFeatures.ts b/src/spec-node/containerFeatures.ts index c7e42a56a..d8912967c 100644 --- a/src/spec-node/containerFeatures.ts +++ b/src/spec-node/containerFeatures.ts @@ -11,7 +11,7 @@ import { LogLevel, makeLog } from '../spec-utils/log'; import { FeaturesConfig, getContainerFeaturesBaseDockerFile, getFeatureInstallWrapperScript, getFeatureLayers, getFeatureMainValue, getFeatureValueObject, generateFeaturesConfig, Feature, generateContainerEnvs } from '../spec-configuration/containerFeaturesConfiguration'; import { readLocalFile } from '../spec-utils/pfs'; import { includeAllConfiguredFeatures } from '../spec-utils/product'; -import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig } from './utils'; +import { createFeaturesTempFolder, DockerResolverParameters, getCacheFolder, getFolderImageName, getEmptyContextFolder, SubstitutedConfig, isBuildxCacheToInline } from './utils'; import { isEarlierVersion, parseVersion, runCommandNoPty } from '../spec-common/commonUtils'; import { getDevcontainerMetadata, getDevcontainerMetadataLabel, getImageBuildInfoFromImage, ImageBuildInfo, ImageMetadataEntry, imageMetadataLabel, MergedDevContainerConfig } from './imageMetadata'; import { supportsBuildContexts } from './dockerfileUtils'; @@ -85,6 +85,9 @@ export async function extendImage(params: DockerResolverParameters, config: Subs if (params.buildxCacheTo) { args.push('--cache-to', params.buildxCacheTo); } + if (!isBuildxCacheToInline(params.buildxCacheTo)) { + args.push('--build-arg', 'BUILDKIT_INLINE_CACHE=1'); + } if (!params.buildNoCache) { params.additionalCacheFroms.forEach(cacheFrom => args.push('--cache-from', cacheFrom)); } @@ -144,8 +147,8 @@ export async function getExtendImageBuildInfo(params: DockerResolverParameters, const platform = params.common.cliHost.platform; const cacheFolder = await getCacheFolder(params.common.cliHost); - const { experimentalLockfile, experimentalFrozenLockfile } = params; - const featuresConfig = await generateFeaturesConfig({ ...params.common, platform, cacheFolder, experimentalLockfile, experimentalFrozenLockfile }, dstFolder, config.config, additionalFeatures); + const { noLockfile, frozenLockfile } = params; + const featuresConfig = await generateFeaturesConfig({ ...params.common, platform, cacheFolder, noLockfile, frozenLockfile }, dstFolder, config.config, additionalFeatures); if (!featuresConfig) { if (canAddLabelsToContainer && !imageBuildInfo.dockerfile) { return { @@ -154,7 +157,7 @@ export async function getExtendImageBuildInfo(params: DockerResolverParameters, } }; } - return { featureBuildInfo: getImageBuildOptions(params, config, dstFolder, baseName, imageBuildInfo) }; + return { featureBuildInfo: await getImageBuildOptions(params, config, dstFolder, baseName, imageBuildInfo) }; } // Generates the end configuration. @@ -193,24 +196,24 @@ export interface ImageBuildOptions { securityOpts: string[]; } -function getImageBuildOptions(params: DockerResolverParameters, config: SubstitutedConfig, dstFolder: string, baseName: string, imageBuildInfo: ImageBuildInfo): ImageBuildOptions { - const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax; - return { - dstFolder, - dockerfileContent: ` +async function getImageBuildOptions(params: DockerResolverParameters, config: SubstitutedConfig, dstFolder: string, baseName: string, imageBuildInfo: ImageBuildInfo): Promise { + const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax; + return { + dstFolder, + dockerfileContent: ` FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_target_stage ${getDevcontainerMetadataLabel(getDevcontainerMetadata(imageBuildInfo.metadata, config, { featureSets: [] }, [], getOmitDevcontainerPropertyOverride(params.common)))} `, - overrideTarget: 'dev_containers_target_stage', - dockerfilePrefixContent: `${syntax ? `# syntax=${syntax}` : ''} - ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder + overrideTarget: 'dev_containers_target_stage', + dockerfilePrefixContent: `${syntax ? `# syntax=${syntax}` : ''} + ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder `, - buildArgs: { - _DEV_CONTAINERS_BASE_IMAGE: baseName, - } as Record, - buildKitContexts: {} as Record, - securityOpts: [], - }; + buildArgs: { + _DEV_CONTAINERS_BASE_IMAGE: baseName, + } as Record, + buildKitContexts: {} as Record, + securityOpts: [], + }; } function getOmitDevcontainerPropertyOverride(resolverParams: { omitConfigRemotEnvFromMetadata?: boolean }): (keyof DevContainerConfig & keyof ImageMetadataEntry)[] { @@ -241,7 +244,10 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont const useBuildKitBuildContexts = buildKitVersionParsed ? !isEarlierVersion(buildKitVersionParsed, minRequiredVersion) : false; const buildContentImageName = 'dev_container_feature_content_temp'; const disableSELinuxLabels = useBuildKitBuildContexts && await isUsingSELinuxLabels(params); - + // Access Docker engine version + const dockerEngineVersionParsed = params.dockerEngineVersion?.versionMatch ? parseVersion(params.dockerEngineVersion.versionMatch) : undefined; + const minDockerEngineVersion = [23, 0, 0]; + const skipDefaultSyntax = dockerEngineVersionParsed ? !isEarlierVersion(dockerEngineVersionParsed, minDockerEngineVersion) : false; const omitPropertyOverride = params.common.skipPersistingCustomizationsFromFeatures ? ['customizations'] : []; const imageMetadata = getDevcontainerMetadata(imageBuildInfo.metadata, devContainerConfig, featuresConfig, omitPropertyOverride, getOmitDevcontainerPropertyOverride(params.common)); const { containerUser, remoteUser } = findContainerUsers(imageMetadata, composeServiceUser, imageBuildInfo.user); @@ -262,11 +268,12 @@ async function getFeaturesBuildOptions(params: DockerResolverParameters, devCont .replace('#{devcontainerMetadata}', getDevcontainerMetadataLabel(imageMetadata)) .replace('#{containerEnvMetadata}', generateContainerEnvs(devContainerConfig.config.containerEnv, true)) ; - const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax; - const omitSyntaxDirective = common.omitSyntaxDirective; // Can be removed when https://github.com/moby/buildkit/issues/4556 is fixed - const dockerfilePrefixContent = `${omitSyntaxDirective ? '' : - useBuildKitBuildContexts && !(imageBuildInfo.dockerfile && supportsBuildContexts(imageBuildInfo.dockerfile)) ? '# syntax=docker/dockerfile:1.4' : - syntax ? `# syntax=${syntax}` : ''} + const syntax = imageBuildInfo.dockerfile?.preamble.directives.syntax; + const omitSyntaxDirective = common.omitSyntaxDirective; // Can be removed when https://github.com/moby/buildkit/issues/4556 is fixed + const dockerfilePrefixContent = `${omitSyntaxDirective ? '' : + skipDefaultSyntax ? (syntax ? `# syntax=${syntax}` : '') : + useBuildKitBuildContexts && !(imageBuildInfo.dockerfile && supportsBuildContexts(imageBuildInfo.dockerfile)) ? '# syntax=docker/dockerfile:1.4' : + syntax ? `# syntax=${syntax}` : ''} ARG _DEV_CONTAINERS_BASE_IMAGE=placeholder `; diff --git a/src/spec-node/devContainers.ts b/src/spec-node/devContainers.ts index 493d0dc22..d647bb614 100644 --- a/src/spec-node/devContainers.ts +++ b/src/spec-node/devContainers.ts @@ -17,7 +17,7 @@ import { LogLevel, LogDimensions, toErrorText, createCombinedLog, createTerminal import { dockerComposeCLIConfig } from './dockerCompose'; import { Mount } from '../spec-configuration/containerFeaturesConfiguration'; import { getPackageConfig, PackageConfiguration } from '../spec-utils/product'; -import { dockerBuildKitVersion, isPodman } from '../spec-shutdown/dockerUtils'; +import { dockerBuildKitVersion, dockerEngineVersion, isPodman } from '../spec-shutdown/dockerUtils'; import { Event } from '../spec-utils/event'; @@ -30,6 +30,7 @@ export interface ProvisionOptions { workspaceMountConsistency?: BindMountConsistency; gpuAvailability?: GPUAvailability; mountWorkspaceGitRoot: boolean; + mountGitWorktreeCommonDir: boolean; configFile: URI | undefined; overrideConfigFile: URI | undefined; logLevel: LogLevel; @@ -67,8 +68,8 @@ export interface ProvisionOptions { installCommand?: string; targetPath?: string; }; - experimentalLockfile?: boolean; - experimentalFrozenLockfile?: boolean; + noLockfile?: boolean; + frozenLockfile?: boolean; secretsP?: Promise>; omitSyntaxDirective?: boolean; includeConfig?: boolean; @@ -102,7 +103,7 @@ export async function launch(options: ProvisionOptions, providedIdLabels: string } export async function createDockerParams(options: ProvisionOptions, disposables: (() => Promise | undefined)[]): Promise { - const { persistedFolder, additionalMounts, updateRemoteUserUIDDefault, containerDataFolder, containerSystemDataFolder, workspaceMountConsistency, gpuAvailability, mountWorkspaceGitRoot, remoteEnv, experimentalLockfile, experimentalFrozenLockfile, omitLoggerHeader, secretsP } = options; + const { persistedFolder, additionalMounts, updateRemoteUserUIDDefault, containerDataFolder, containerSystemDataFolder, workspaceMountConsistency, gpuAvailability, mountWorkspaceGitRoot, mountGitWorktreeCommonDir, remoteEnv, noLockfile, frozenLockfile, omitLoggerHeader, secretsP } = options; let parsedAuthority: DevContainerAuthority | undefined; if (options.workspaceFolder) { parsedAuthority = { hostPath: options.workspaceFolder } as DevContainerAuthority; @@ -171,7 +172,12 @@ export async function createDockerParams(options: ProvisionOptions, disposables: output: common.output, }, dockerPath, dockerComposePath); - const platformInfo = (() => { + const buildPlatformInfo = { + os: mapNodeOSToGOOS(cliHost.platform), + arch: mapNodeArchitectureToGOARCH(cliHost.arch), + }; + + const targetPlatformInfo = (() => { if (common.buildxPlatform) { const slash1 = common.buildxPlatform.indexOf('/'); const slash2 = common.buildxPlatform.indexOf('/', slash1 + 1); @@ -203,8 +209,20 @@ export async function createDockerParams(options: ProvisionOptions, disposables: dockerComposeCLI, env: cliHost.env, output, - platformInfo + buildPlatformInfo, + targetPlatformInfo })); + + const dockerEngineVer = await dockerEngineVersion({ + cliHost, + dockerCLI: dockerPath, + dockerComposeCLI, + env: cliHost.env, + output, + buildPlatformInfo, + targetPlatformInfo + }); + return { common, parsedAuthority, @@ -215,6 +233,7 @@ export async function createDockerParams(options: ProvisionOptions, disposables: workspaceMountConsistencyDefault: workspaceMountConsistency, gpuAvailability: gpuAvailability || 'detect', mountWorkspaceGitRoot, + mountGitWorktreeCommonDir, updateRemoteUserUIDOnMacOS: false, cacheMount: 'bind', removeOnStartup: options.removeExistingContainer, @@ -225,15 +244,17 @@ export async function createDockerParams(options: ProvisionOptions, disposables: updateRemoteUserUIDDefault, additionalCacheFroms: options.additionalCacheFroms, buildKitVersion, + dockerEngineVersion: dockerEngineVer, isTTY: process.stdout.isTTY || options.logFormat === 'json', - experimentalLockfile, - experimentalFrozenLockfile, + noLockfile, + frozenLockfile, buildxPlatform: common.buildxPlatform, buildxPush: common.buildxPush, additionalLabels: options.additionalLabels, buildxOutput: common.buildxOutput, buildxCacheTo: common.buildxCacheTo, - platformInfo + buildPlatformInfo, + targetPlatformInfo }; } diff --git a/src/spec-node/devContainersSpecCLI.ts b/src/spec-node/devContainersSpecCLI.ts index 59136695d..832e9603f 100644 --- a/src/spec-node/devContainersSpecCLI.ts +++ b/src/spec-node/devContainersSpecCLI.ts @@ -103,10 +103,11 @@ function provisionOptions(y: Argv) { 'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' }, 'container-data-folder': { type: 'string', description: 'Container data folder where user data inside the container will be stored.' }, 'container-system-data-folder': { type: 'string', description: 'Container system data folder where system data inside the container will be stored.' }, - 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --id-label, --override-config, and --workspace-folder are not provided, this defaults to the current directory.' }, 'workspace-mount-consistency': { choices: ['consistent' as 'consistent', 'cached' as 'cached', 'delegated' as 'delegated'], default: 'cached' as 'cached', description: 'Workspace mount consistency.' }, 'gpu-availability': { choices: ['all' as 'all', 'detect' as 'detect', 'none' as 'none'], default: 'detect' as 'detect', description: 'Availability of GPUs in case the dev container requires any. `all` expects a GPU to be available.' }, 'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' }, + 'mount-git-worktree-common-dir': { type: 'boolean', default: false, description: 'Mount the Git worktree common dir for Git operations to work in the container. This requires the worktree to be created with relative paths (`git worktree add --relative-paths`).' }, 'id-label': { type: 'string', description: 'Id label(s) of the format name=value. These will be set on the container and used to query for an existing container. If no --id-label is given, one will be inferred from the --workspace-folder path.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, 'override-config': { type: 'string', description: 'devcontainer.json path to override any devcontainer.json in the workspace folder (or built-in configuration). This is required when there is no devcontainer.json otherwise.' }, @@ -139,6 +140,8 @@ function provisionOptions(y: Argv) { 'secrets-file': { type: 'string', description: 'Path to a json file containing secret environment variables as key-value pairs.' }, 'experimental-lockfile': { type: 'boolean', default: false, hidden: true, description: 'Write lockfile' }, 'experimental-frozen-lockfile': { type: 'boolean', default: false, hidden: true, description: 'Ensure lockfile remains unchanged' }, + 'no-lockfile': { type: 'boolean', default: false, description: 'Disable lockfile generation and verification.' }, + 'frozen-lockfile': { type: 'boolean', default: false, description: 'Ensure lockfile exists and remains unchanged; fail otherwise.' }, 'omit-syntax-directive': { type: 'boolean', default: false, hidden: true, description: 'Omit Dockerfile syntax directives' }, 'include-configuration': { type: 'boolean', default: false, description: 'Include configuration in result.' }, 'include-merged-configuration': { type: 'boolean', default: false, description: 'Include merged configuration in result.' }, @@ -148,11 +151,9 @@ function provisionOptions(y: Argv) { if (idLabels?.some(idLabel => !/.+=.+/.test(idLabel))) { throw new Error('Unmatched argument format: id-label must match ='); } - if (!(argv['workspace-folder'] || argv['id-label'])) { - throw new Error('Missing required argument: workspace-folder or id-label'); - } - if (!(argv['workspace-folder'] || argv['override-config'])) { - throw new Error('Missing required argument: workspace-folder or override-config'); + // Default workspace-folder to current directory if not provided and no id-label or override-config + if (!argv['workspace-folder'] && !argv['id-label'] && !argv['override-config']) { + argv['workspace-folder'] = process.cwd(); } const mounts = (argv.mount && (Array.isArray(argv.mount) ? argv.mount : [argv.mount])) as string[] | undefined; if (mounts?.some(mount => !mountRegex.test(mount))) { @@ -162,6 +163,15 @@ function provisionOptions(y: Argv) { if (remoteEnvs?.some(remoteEnv => !/.+=.*/.test(remoteEnv))) { throw new Error('Unmatched argument format: remote-env must match ='); } + if (argv['no-lockfile'] && argv['frozen-lockfile']) { + throw new Error('--no-lockfile and --frozen-lockfile are mutually exclusive.'); + } + if (argv['no-lockfile'] && argv['experimental-frozen-lockfile']) { + throw new Error('--no-lockfile and --experimental-frozen-lockfile are mutually exclusive.'); + } + if (argv['no-lockfile'] && argv['experimental-lockfile']) { + throw new Error('--no-lockfile and --experimental-lockfile are mutually exclusive.'); + } return true; }); } @@ -182,6 +192,7 @@ async function provision({ 'workspace-mount-consistency': workspaceMountConsistency, 'gpu-availability': gpuAvailability, 'mount-workspace-git-root': mountWorkspaceGitRoot, + 'mount-git-worktree-common-dir': mountGitWorktreeCommonDir, 'id-label': idLabel, config, 'override-config': overrideConfig, @@ -213,11 +224,16 @@ async function provision({ 'secrets-file': secretsFile, 'experimental-lockfile': experimentalLockfile, 'experimental-frozen-lockfile': experimentalFrozenLockfile, + 'no-lockfile': noLockfile, + 'frozen-lockfile': frozenLockfile, 'omit-syntax-directive': omitSyntaxDirective, 'include-configuration': includeConfig, 'include-merged-configuration': includeMergedConfig, }: ProvisionArgs) { + warnDeprecatedLockfileFlags(experimentalLockfile, experimentalFrozenLockfile); + const effectiveFrozenLockfile = frozenLockfile || experimentalFrozenLockfile; + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : undefined; const addRemoteEnvs = addRemoteEnv ? (Array.isArray(addRemoteEnv) ? addRemoteEnv as string[] : [addRemoteEnv]) : []; const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : []; @@ -237,6 +253,7 @@ async function provision({ workspaceMountConsistency, gpuAvailability, mountWorkspaceGitRoot, + mountGitWorktreeCommonDir, configFile: config ? URI.file(path.resolve(process.cwd(), config)) : undefined, overrideConfigFile: overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : undefined, logLevel: mapLogLevel(logLevel), @@ -281,8 +298,8 @@ async function provision({ containerSessionDataFolder, skipPersistingCustomizationsFromFeatures: false, omitConfigRemotEnvFromMetadata, - experimentalLockfile, - experimentalFrozenLockfile, + noLockfile, + frozenLockfile: effectiveFrozenLockfile, omitSyntaxDirective, includeConfig, includeMergedConfig, @@ -420,6 +437,7 @@ async function doSetUp({ containerSystemDataFolder, workspaceFolder: undefined, mountWorkspaceGitRoot: false, + mountGitWorktreeCommonDir: false, configFile, overrideConfigFile: undefined, logLevel: mapLogLevel(logLevel), @@ -456,7 +474,7 @@ async function doSetUp({ const { common } = params; const { cliHost, output } = common; - const configs = configFile && await readDevContainerConfigFile(cliHost, undefined, configFile, params.mountWorkspaceGitRoot, output, undefined, undefined); + const configs = configFile && await readDevContainerConfigFile(cliHost, undefined, configFile, params.mountWorkspaceGitRoot, params.mountGitWorktreeCommonDir, output, undefined, undefined); if (configFile && !configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile, cliHost.platform)}) not found.` }); } @@ -507,7 +525,7 @@ function buildOptions(y: Argv) { 'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' }, 'docker-path': { type: 'string', description: 'Docker CLI path.' }, 'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' }, - 'workspace-folder': { type: 'string', required: true, description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If not provided, defaults to the current directory.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, 'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level.' }, 'log-format': { choices: ['text' as 'text', 'json' as 'json'], default: 'text' as 'text', description: 'Log format.' }, @@ -525,8 +543,22 @@ function buildOptions(y: Argv) { 'skip-persisting-customizations-from-features': { type: 'boolean', default: false, hidden: true, description: 'Do not save customizations from referenced Features as image metadata' }, 'experimental-lockfile': { type: 'boolean', default: false, hidden: true, description: 'Write lockfile' }, 'experimental-frozen-lockfile': { type: 'boolean', default: false, hidden: true, description: 'Ensure lockfile remains unchanged' }, + 'no-lockfile': { type: 'boolean', default: false, description: 'Disable lockfile generation and verification.' }, + 'frozen-lockfile': { type: 'boolean', default: false, description: 'Ensure lockfile exists and remains unchanged; fail otherwise.' }, 'omit-syntax-directive': { type: 'boolean', default: false, hidden: true, description: 'Omit Dockerfile syntax directives' }, - }); + }) + .check(argv => { + if (argv['no-lockfile'] && argv['frozen-lockfile']) { + throw new Error('--no-lockfile and --frozen-lockfile are mutually exclusive.'); + } + if (argv['no-lockfile'] && argv['experimental-frozen-lockfile']) { + throw new Error('--no-lockfile and --experimental-frozen-lockfile are mutually exclusive.'); + } + if (argv['no-lockfile'] && argv['experimental-lockfile']) { + throw new Error('--no-lockfile and --experimental-lockfile are mutually exclusive.'); + } + return true; + }); } type BuildArgs = UnpackArgv>; @@ -567,14 +599,19 @@ async function doBuild({ 'skip-persisting-customizations-from-features': skipPersistingCustomizationsFromFeatures, 'experimental-lockfile': experimentalLockfile, 'experimental-frozen-lockfile': experimentalFrozenLockfile, + 'no-lockfile': noLockfile, + 'frozen-lockfile': frozenLockfile, 'omit-syntax-directive': omitSyntaxDirective, }: BuildArgs) { + warnDeprecatedLockfileFlags(experimentalLockfile, experimentalFrozenLockfile); + const effectiveFrozenLockfile = frozenLockfile || experimentalFrozenLockfile; + const disposables: (() => Promise | undefined)[] = []; const dispose = async () => { await Promise.all(disposables.map(d => d())); }; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd(); const configFile: URI | undefined = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const overrideConfigFile: URI | undefined = /* overrideConfig ? URI.file(path.resolve(process.cwd(), overrideConfig)) : */ undefined; const addCacheFroms = addCacheFrom ? (Array.isArray(addCacheFrom) ? addCacheFrom as string[] : [addCacheFrom]) : []; @@ -586,6 +623,7 @@ async function doBuild({ containerSystemDataFolder: undefined, workspaceFolder, mountWorkspaceGitRoot: false, + mountGitWorktreeCommonDir: false, configFile, overrideConfigFile, logLevel: mapLogLevel(logLevel), @@ -614,8 +652,8 @@ async function doBuild({ skipPostAttach: true, skipPersistingCustomizationsFromFeatures: skipPersistingCustomizationsFromFeatures, dotfiles: {}, - experimentalLockfile, - experimentalFrozenLockfile, + noLockfile, + frozenLockfile: effectiveFrozenLockfile, omitSyntaxDirective, }, disposables); @@ -626,7 +664,7 @@ async function doBuild({ ? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath) || (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined)) : overrideConfigFile; - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, output, undefined, overrideConfigFile) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, params.mountGitWorktreeCommonDir, output, undefined, overrideConfigFile) || undefined; if (!configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } @@ -638,7 +676,7 @@ async function doBuild({ throw new ContainerError({ description: '--push true cannot be used with --output.' }); } - const buildParams: DockerCLIParameters = { cliHost, dockerCLI: params.dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo }; + const buildParams: DockerCLIParameters = { cliHost, dockerCLI: params.dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo }; await ensureNoDisallowedFeatures(buildParams, config, additionalFeatures, undefined); // Support multiple use of `--image-name` @@ -752,8 +790,9 @@ function runUserCommandsOptions(y: Argv) { 'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' }, 'container-data-folder': { type: 'string', description: 'Container data folder where user data inside the container will be stored.' }, 'container-system-data-folder': { type: 'string', description: 'Container system data folder where system data inside the container will be stored.' }, - 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path.The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' }, 'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' }, + 'mount-git-worktree-common-dir': { type: 'boolean', default: false, description: 'Mount the Git worktree common dir for Git operations to work in the container. This requires the worktree to be created with relative paths (`git worktree add --relative-paths`).' }, 'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' }, 'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, @@ -785,7 +824,7 @@ function runUserCommandsOptions(y: Argv) { throw new Error('Unmatched argument format: remote-env must match ='); } if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) { - throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.'); + argv['workspace-folder'] = process.cwd(); } return true; }); @@ -814,6 +853,7 @@ async function doRunUserCommands({ 'container-system-data-folder': containerSystemDataFolder, 'workspace-folder': workspaceFolderArg, 'mount-workspace-git-root': mountWorkspaceGitRoot, + 'mount-git-worktree-common-dir': mountGitWorktreeCommonDir, 'container-id': containerId, 'id-label': idLabel, config: configParam, @@ -857,6 +897,7 @@ async function doRunUserCommands({ containerSystemDataFolder, workspaceFolder, mountWorkspaceGitRoot, + mountGitWorktreeCommonDir, configFile, overrideConfigFile, logLevel: mapLogLevel(logLevel), @@ -900,7 +941,7 @@ async function doRunUserCommands({ ? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath) || (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined)) : overrideConfigFile; - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, output, undefined, overrideConfigFile) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, params.mountGitWorktreeCommonDir, output, undefined, overrideConfigFile) || undefined; if ((configFile || workspaceFolder || overrideConfigFile) && !configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } @@ -954,8 +995,9 @@ function readConfigurationOptions(y: Argv) { 'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' }, 'docker-path': { type: 'string', description: 'Docker CLI path.' }, 'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' }, - 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' }, 'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' }, + 'mount-git-worktree-common-dir': { type: 'boolean', default: false, description: 'Mount the Git worktree common dir for Git operations to work in the container. This requires the worktree to be created with relative paths (`git worktree add --relative-paths`).' }, 'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' }, 'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, @@ -975,7 +1017,7 @@ function readConfigurationOptions(y: Argv) { throw new Error('Unmatched argument format: id-label must match ='); } if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) { - throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.'); + argv['workspace-folder'] = process.cwd(); } return true; }); @@ -993,6 +1035,7 @@ async function readConfiguration({ 'docker-compose-path': dockerComposePath, 'workspace-folder': workspaceFolderArg, 'mount-workspace-git-root': mountWorkspaceGitRoot, + 'mount-git-worktree-common-dir': mountGitWorktreeCommonDir, config: configParam, 'override-config': overrideConfig, 'container-id': containerId, @@ -1033,7 +1076,7 @@ async function readConfiguration({ ? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath) || (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined)) : overrideConfigFile; - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, mountWorkspaceGitRoot, output, undefined, overrideConfigFile) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, mountWorkspaceGitRoot, mountGitWorktreeCommonDir, output, undefined, overrideConfigFile) || undefined; if ((configFile || workspaceFolder || overrideConfigFile) && !configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } @@ -1050,16 +1093,18 @@ async function readConfiguration({ env: cliHost.env, output, }, dockerCLI, dockerComposePath || 'docker-compose'); + const buildPlatformInfo = { + os: mapNodeOSToGOOS(cliHost.platform), + arch: mapNodeArchitectureToGOARCH(cliHost.arch), + }; const params: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env: cliHost.env, output, - platformInfo: { - os: mapNodeOSToGOOS(cliHost.platform), - arch: mapNodeArchitectureToGOARCH(cliHost.arch), - } + buildPlatformInfo, + targetPlatformInfo: buildPlatformInfo }; const { container, idLabels } = await findContainerAndIdLabels(params, containerId, providedIdLabels, workspaceFolder, configPath?.fsPath); if (container) { @@ -1107,7 +1152,7 @@ async function readConfiguration({ function outdatedOptions(y: Argv) { return y.options({ 'user-data-folder': { type: 'string', description: 'Host path to a directory that is intended to be persisted and share state between sessions.' }, - 'workspace-folder': { type: 'string', required: true, description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --workspace-folder is not provided, defaults to the current directory.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, 'output-format': { choices: ['text' as 'text', 'json' as 'json'], default: 'text', description: 'Output format.' }, 'log-level': { choices: ['info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'info' as 'info', description: 'Log level for the --terminal-log-file. When set to trace, the log level for --log-file will also be set to trace.' }, @@ -1139,7 +1184,7 @@ async function outdated({ }; let output: Log | undefined; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd(); const configFile = configParam ? URI.file(path.resolve(process.cwd(), configParam)) : undefined; const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, logFormat === 'text'); const extensionPath = path.join(__dirname, '..', '..'); @@ -1154,7 +1199,7 @@ async function outdated({ const workspace = workspaceFromPath(cliHost.path, workspaceFolder); const configPath = configFile ? configFile : await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath); - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, true, output) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, true, false, output) || undefined; if (!configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } @@ -1209,8 +1254,9 @@ function execOptions(y: Argv) { 'docker-compose-path': { type: 'string', description: 'Docker Compose CLI path.' }, 'container-data-folder': { type: 'string', description: 'Container data folder where user data inside the container will be stored.' }, 'container-system-data-folder': { type: 'string', description: 'Container system data folder where system data inside the container will be stored.' }, - 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path.' }, + 'workspace-folder': { type: 'string', description: 'Workspace folder path. The devcontainer.json will be looked up relative to this path. If --container-id, --id-label, and --workspace-folder are not provided, this defaults to the current directory.' }, 'mount-workspace-git-root': { type: 'boolean', default: true, description: 'Mount the workspace using its Git root.' }, + 'mount-git-worktree-common-dir': { type: 'boolean', default: false, description: 'Mount the Git worktree common dir for Git operations to work in the container. This requires the worktree to be created with relative paths (`git worktree add --relative-paths`).' }, 'container-id': { type: 'string', description: 'Id of the container to run the user commands for.' }, 'id-label': { type: 'string', description: 'Id label(s) of the format name=value. If no --container-id is given the id labels will be used to look up the container. If no --id-label is given, one will be inferred from the --workspace-folder path.' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, @@ -1243,7 +1289,7 @@ function execOptions(y: Argv) { throw new Error('Unmatched argument format: remote-env must match ='); } if (!argv['container-id'] && !idLabels?.length && !argv['workspace-folder']) { - throw new Error('Missing required argument: One of --container-id, --id-label or --workspace-folder is required.'); + argv['workspace-folder'] = process.cwd(); } return true; }); @@ -1272,6 +1318,7 @@ export async function doExec({ 'container-system-data-folder': containerSystemDataFolder, 'workspace-folder': workspaceFolderArg, 'mount-workspace-git-root': mountWorkspaceGitRoot, + 'mount-git-worktree-common-dir': mountGitWorktreeCommonDir, 'container-id': containerId, 'id-label': idLabel, config: configParam, @@ -1304,6 +1351,7 @@ export async function doExec({ containerSystemDataFolder, workspaceFolder, mountWorkspaceGitRoot, + mountGitWorktreeCommonDir, configFile, overrideConfigFile, logLevel: mapLogLevel(logLevel), @@ -1344,7 +1392,7 @@ export async function doExec({ ? (await getDevContainerConfigPathIn(cliHost, workspace.configFolderPath) || (overrideConfigFile ? getDefaultDevContainerConfigPath(cliHost, workspace.configFolderPath) : undefined)) : overrideConfigFile; - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, output, undefined, overrideConfigFile) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, params.mountWorkspaceGitRoot, params.mountGitWorktreeCommonDir, output, undefined, overrideConfigFile) || undefined; if ((configFile || workspaceFolder || overrideConfigFile) && !configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } @@ -1429,3 +1477,12 @@ async function readSecretsFromFile(params: { output?: Log; secretsFile?: string; }); } } + +function warnDeprecatedLockfileFlags(experimentalLockfile: boolean, experimentalFrozenLockfile: boolean) { + if (experimentalLockfile) { + process.stderr.write('Warning: --experimental-lockfile is deprecated. Lockfiles are now enabled by default.\n'); + } + if (experimentalFrozenLockfile) { + process.stderr.write('Warning: --experimental-frozen-lockfile is deprecated. Use --frozen-lockfile instead.\n'); + } +} diff --git a/src/spec-node/dockerCompose.ts b/src/spec-node/dockerCompose.ts index 4c20ebd68..8093464cc 100644 --- a/src/spec-node/dockerCompose.ts +++ b/src/spec-node/dockerCompose.ts @@ -27,7 +27,7 @@ const serviceLabel = 'com.docker.compose.service'; export async function openDockerComposeDevContainer(params: DockerResolverParameters, workspace: Workspace, config: SubstitutedConfig, idLabels: string[], additionalFeatures: Record>): Promise { const { common, dockerCLI, dockerComposeCLI } = params; const { cliHost, env, output } = common; - const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, platformInfo: params.platformInfo }; + const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo }; return _openDockerComposeDevContainer(params, buildParams, workspace, config, getRemoteWorkspaceFolder(config.config), idLabels, additionalFeatures); } @@ -155,7 +155,7 @@ export async function buildAndExtendDockerCompose(configWithRaw: SubstitutedConf const { cliHost, env, output } = common; const { config } = configWithRaw; - const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI: dockerComposeCLIFunc, env, output, platformInfo: params.platformInfo }; + const cliParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI: dockerComposeCLIFunc, env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo }; const composeConfig = await readDockerComposeConfig(cliParams, localComposeFiles, envFile); const composeService = composeConfig.services[config.service]; diff --git a/src/spec-node/dockerfileUtils.ts b/src/spec-node/dockerfileUtils.ts index 2a07023aa..532324f2b 100644 --- a/src/spec-node/dockerfileUtils.ts +++ b/src/spec-node/dockerfileUtils.ts @@ -81,7 +81,7 @@ export function extractDockerfile(dockerfile: string): Dockerfile { } as Dockerfile; } -export function findUserStatement(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, target: string | undefined) { +export function findUserStatement(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, globalBuildxPlatformArgs: Record = {}, target: string | undefined) { let stage: Stage | undefined = target ? dockerfile.stagesByLabel[target] : dockerfile.stages[dockerfile.stages.length - 1]; const seen = new Set(); while (stage) { @@ -92,15 +92,15 @@ export function findUserStatement(dockerfile: Dockerfile, buildArgs: Record i.instruction === 'USER'); if (i !== -1) { - return replaceVariables(dockerfile, buildArgs, baseImageEnv, stage.instructions[i].name, stage, i) || undefined; + return replaceVariables(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, stage.instructions[i].name, stage, i) || undefined; } - const image = replaceVariables(dockerfile, buildArgs, baseImageEnv, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length); + const image = replaceVariables(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length); stage = dockerfile.stagesByLabel[image]; } return undefined; } -export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record, target: string | undefined) { +export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record, target: string | undefined, globalBuildxPlatformArgs: Record = {}) { let stage: Stage | undefined = target ? dockerfile.stagesByLabel[target] : dockerfile.stages[dockerfile.stages.length - 1]; const seen = new Set(); while (stage) { @@ -109,7 +109,7 @@ export function findBaseImage(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, str: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number) { +function replaceVariables(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, globalBuildxPlatformArgs: Record = {}, str: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number) { return [...str.matchAll(argumentExpression)] .map(match => { const variable = match.groups!.variable; const isVarExp = match.groups!.isVarExp ? true : false; - let value = findValue(dockerfile, buildArgs, baseImageEnv, variable, stage, beforeInstructionIndex) || ''; + let value = findValue(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, variable, stage, beforeInstructionIndex) || ''; if (isVarExp) { // Handle replacing variable expressions (${var:+word}) if they exist const option = match.groups!.option; @@ -178,7 +178,7 @@ function replaceVariables(dockerfile: Dockerfile, buildArgs: Record str.substring(0, begin) + value + str.substring(end), str); } -function findValue(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, variable: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number): string | undefined { +function findValue(dockerfile: Dockerfile, buildArgs: Record, baseImageEnv: Record, globalBuildxPlatformArgs: Record = {}, variable: string, stage: { from?: From; instructions: Instruction[] }, beforeInstructionIndex: number): string | undefined { let considerArg = true; const seen = new Set(); while (true) { @@ -191,22 +191,22 @@ function findValue(dockerfile: Dockerfile, buildArgs: Record, ba if (i !== -1) { const instruction = stage.instructions[i]; if (instruction.instruction === 'ENV') { - return replaceVariables(dockerfile, buildArgs, baseImageEnv, instruction.value!, stage, i); + return replaceVariables(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, instruction.value!, stage, i); } if (instruction.instruction === 'ARG') { - return replaceVariables(dockerfile, buildArgs, baseImageEnv, buildArgs[instruction.name] ?? instruction.value, stage, i); + return replaceVariables(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, buildArgs[instruction.name] ?? instruction.value, stage, i); } } if (!stage.from) { - const value = baseImageEnv[variable]; + const value = baseImageEnv[variable] ?? globalBuildxPlatformArgs[variable]; if (typeof value === 'string') { return value; } return undefined; } - const image = replaceVariables(dockerfile, buildArgs, baseImageEnv, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length); + const image = replaceVariables(dockerfile, buildArgs, baseImageEnv, globalBuildxPlatformArgs, stage.from.image, dockerfile.preamble, dockerfile.preamble.instructions.length); stage = dockerfile.stagesByLabel[image] || dockerfile.preamble; beforeInstructionIndex = stage.instructions.length; considerArg = stage === dockerfile.preamble; diff --git a/src/spec-node/featureUtils.ts b/src/spec-node/featureUtils.ts index 0b3fabc01..a36205295 100644 --- a/src/spec-node/featureUtils.ts +++ b/src/spec-node/featureUtils.ts @@ -9,5 +9,5 @@ export async function readFeaturesConfig(params: DockerCLIParameters, pkg: Packa const { cwd, env, platform } = cliHost; const featuresTmpFolder = await createFeaturesTempFolder({ cliHost, package: pkg }); const cacheFolder = await getCacheFolder(cliHost); - return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform }, featuresTmpFolder, config, additionalFeatures); + return generateFeaturesConfig({ extensionPath, cacheFolder, cwd, output, env, skipFeatureAutoMapping, platform, noLockfile: true }, featuresTmpFolder, config, additionalFeatures); } \ No newline at end of file diff --git a/src/spec-node/featuresCLI/resolveDependencies.ts b/src/spec-node/featuresCLI/resolveDependencies.ts index 93e569ff6..3c24c3788 100644 --- a/src/spec-node/featuresCLI/resolveDependencies.ts +++ b/src/spec-node/featuresCLI/resolveDependencies.ts @@ -30,7 +30,7 @@ export function featuresResolveDependenciesOptions(y: Argv) { return y .options({ 'log-level': { choices: ['error' as 'error', 'info' as 'info', 'debug' as 'debug', 'trace' as 'trace'], default: 'error' as 'error', description: 'Log level.' }, - 'workspace-folder': { type: 'string', description: 'Workspace folder to use for the configuration.', demandOption: true }, + 'workspace-folder': { type: 'string', description: 'Workspace folder to use for the configuration. If --workspace-folder is not provided, this defaults to the current directory' }, }); } @@ -41,7 +41,7 @@ export function featuresResolveDependenciesHandler(args: featuresResolveDependen } async function featuresResolveDependencies({ - 'workspace-folder': workspaceFolder, + 'workspace-folder': workspaceFolderArg, 'log-level': inputLogLevel, }: featuresResolveDependenciesArgs) { const disposables: (() => Promise | undefined)[] = []; @@ -62,6 +62,8 @@ async function featuresResolveDependencies({ let jsonOutput: JsonOutput = {}; + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd(); + // Detect path to dev container config let configPath = path.join(workspaceFolder, '.devcontainer.json'); if (!(await isLocalFile(configPath))) { @@ -77,7 +79,7 @@ async function featuresResolveDependencies({ const cliHost = await getCLIHost(cwd, loadNativeModule, true); const workspace = workspaceFromPath(cliHost.path, workspaceFolder); const configFile: URI = URI.file(path.resolve(process.cwd(), configPath)); - const configs = await readDevContainerConfigFile(cliHost, workspace, configFile, false, output, undefined, undefined); + const configs = await readDevContainerConfigFile(cliHost, workspace, configFile, false, false, output, undefined, undefined); if (configFile && !configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile, cliHost.platform)}) not found.` }); diff --git a/src/spec-node/featuresCLI/testCommandImpl.ts b/src/spec-node/featuresCLI/testCommandImpl.ts index 558162443..d119885a8 100644 --- a/src/spec-node/featuresCLI/testCommandImpl.ts +++ b/src/spec-node/featuresCLI/testCommandImpl.ts @@ -6,7 +6,7 @@ import { CLIHost } from '../../spec-common/cliHost'; import { launch, ProvisionOptions, createDockerParams } from '../devContainers'; import { doExec } from '../devContainersSpecCLI'; import { LaunchResult, staticExecParams, staticProvisionParams, testLibraryScript } from './utils'; -import { DockerResolverParameters } from '../utils'; +import { DockerResolverParameters, normalizeDevContainerLabelPath } from '../utils'; import { DevContainerConfig } from '../../spec-configuration/configuration'; import { FeaturesTestCommandInput } from './test'; import { cpDirectoryLocal, rmLocal } from '../../spec-utils/pfs'; @@ -546,13 +546,15 @@ async function launchProject(params: DockerResolverParameters, workspaceFolder: const { common } = params; let response = {} as LaunchResult; - const idLabels = [`devcontainer.local_folder=${workspaceFolder}`, `devcontainer.is_test_run=true`]; + const normalizedWorkspaceFolder = normalizeDevContainerLabelPath(process.platform, workspaceFolder); + const idLabels = [`devcontainer.local_folder=${normalizedWorkspaceFolder}`, `devcontainer.is_test_run=true`]; const options: ProvisionOptions = { ...staticProvisionParams, workspaceFolder, additionalLabels: [], logLevel: common.getLogLevel(), mountWorkspaceGitRoot: true, + mountGitWorktreeCommonDir: false, remoteEnv: common.remoteEnv, skipFeatureAutoMapping: common.skipFeatureAutoMapping, skipPersistingCustomizationsFromFeatures: common.skipPersistingCustomizationsFromFeatures, @@ -632,6 +634,7 @@ async function generateDockerParams(workspaceFolder: string, args: FeaturesTestC containerDataFolder: undefined, containerSystemDataFolder: undefined, mountWorkspaceGitRoot: false, + mountGitWorktreeCommonDir: false, configFile: undefined, overrideConfigFile: undefined, logLevel, diff --git a/src/spec-node/featuresCLI/utils.ts b/src/spec-node/featuresCLI/utils.ts index d72cb1705..c981a4c2e 100644 --- a/src/spec-node/featuresCLI/utils.ts +++ b/src/spec-node/featuresCLI/utils.ts @@ -40,6 +40,7 @@ export const staticExecParams = { 'terminal-columns': undefined, 'container-id': undefined, 'mount-workspace-git-root': true, + 'mount-git-worktree-common-dir': false, 'log-level': 'info' as 'info', 'log-format': 'text' as 'text', 'default-user-env-probe': 'loginInteractiveShell' as 'loginInteractiveShell', diff --git a/src/spec-node/imageMetadata.ts b/src/spec-node/imageMetadata.ts index cb3cc5fa6..60884592e 100644 --- a/src/spec-node/imageMetadata.ts +++ b/src/spec-node/imageMetadata.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ContainerError } from '../spec-common/errors'; +import { PlatformInfo } from '../spec-common/commonUtils'; import { LifecycleCommand, LifecycleHooksInstallMap } from '../spec-common/injectHeadless'; import { DevContainerConfig, DevContainerConfigCommand, DevContainerFromDockerComposeConfig, DevContainerFromDockerfileConfig, DevContainerFromImageConfig, getDockerComposeFilePaths, getDockerfilePath, HostGPURequirements, HostRequirements, isDockerFileConfig, PortAttributes, UserEnvProbe } from '../spec-configuration/configuration'; import { Feature, FeaturesConfig, Mount, parseMount, SchemaFeatureLifecycleHooks } from '../spec-configuration/containerFeaturesConfiguration'; @@ -349,7 +350,7 @@ export async function getImageBuildInfo(params: DockerResolverParameters | Docke const cwdEnvFile = cliHost.path.join(cliHost.cwd, '.env'); const envFile = Array.isArray(config.dockerComposeFile) && config.dockerComposeFile.length === 0 && await cliHost.isFile(cwdEnvFile) ? cwdEnvFile : undefined; const composeFiles = await getDockerComposeFilePaths(cliHost, config, cliHost.env, cliHost.cwd); - const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env: cliHost.env, output, platformInfo: params.platformInfo }; + const buildParams: DockerCLIParameters = { cliHost, dockerCLI, dockerComposeCLI, env: cliHost.env, output, buildPlatformInfo: params.buildPlatformInfo, targetPlatformInfo: params.targetPlatformInfo }; const composeConfig = await readDockerComposeConfig(buildParams, composeFiles, envFile); const services = Object.keys(composeConfig.services || {}); @@ -394,18 +395,37 @@ export async function getImageBuildInfoFromImage(params: DockerResolverParameter export async function getImageBuildInfoFromDockerfile(params: DockerResolverParameters | DockerCLIParameters, dockerfile: string, dockerBuildArgs: Record, targetStage: string | undefined, substitute: SubstituteConfig) { const { output } = 'output' in params ? params : params.common; const omitSyntaxDirective = 'common' in params ? !!params.common.omitSyntaxDirective : false; - return internalGetImageBuildInfoFromDockerfile(imageName => inspectDockerImage(params, imageName, true), dockerfile, dockerBuildArgs, targetStage, substitute, output, omitSyntaxDirective); + return internalGetImageBuildInfoFromDockerfile(imageName => inspectDockerImage(params, imageName, true), dockerfile, dockerBuildArgs, targetStage, substitute, output, omitSyntaxDirective, params.buildPlatformInfo, params.targetPlatformInfo); } -export async function internalGetImageBuildInfoFromDockerfile(inspectDockerImage: (imageName: string) => Promise, dockerfileText: string, dockerBuildArgs: Record, targetStage: string | undefined, substitute: SubstituteConfig, output: Log, omitSyntaxDirective: boolean): Promise { +export async function internalGetImageBuildInfoFromDockerfile(inspectDockerImage: (imageName: string) => Promise, dockerfileText: string, dockerBuildArgs: Record, targetStage: string | undefined, substitute: SubstituteConfig, output: Log, omitSyntaxDirective: boolean, buildPlatform: PlatformInfo, targetPlatform: PlatformInfo): Promise { const dockerfile = extractDockerfile(dockerfileText); if (dockerfile.preamble.directives.syntax && omitSyntaxDirective) { output.write(`Omitting syntax directive '${dockerfile.preamble.directives.syntax}' from Dockerfile.`, LogLevel.Trace); delete dockerfile.preamble.directives.syntax; } - const baseImage = findBaseImage(dockerfile, dockerBuildArgs, targetStage); + // https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/reference.md#automatic-platform-args-in-the-global-scope + const globalBuildxPlatformArgs = { + // platform of the node performing the build. + BUILDPLATFORM: [buildPlatform.os, buildPlatform.arch, buildPlatform.variant].filter(Boolean).join("/"), + // OS component of BUILDPLATFORM + BUILDOS: buildPlatform.os, + // architecture component of BUILDPLATFORM + BUILDARCH: buildPlatform.arch, + // variant component of BUILDPLATFORM + BUILDVARIANT: buildPlatform.variant ?? "", + // platform of the build result. Eg linux/amd64, linux/arm/v7, windows/amd64. + TARGETPLATFORM: [targetPlatform.os, targetPlatform.arch, targetPlatform.variant].filter(Boolean).join("/"), + // OS component of TARGETPLATFORM + TARGETOS: targetPlatform.os, + // architecture component of TARGETPLATFORM + TARGETARCH: targetPlatform.arch, + // variant component of TARGETPLATFORM + TARGETVARIANT: targetPlatform.variant ?? "", + }; + const baseImage = findBaseImage(dockerfile, dockerBuildArgs, targetStage, globalBuildxPlatformArgs); const imageDetails = baseImage && await inspectDockerImage(baseImage) || undefined; - const dockerfileUser = findUserStatement(dockerfile, dockerBuildArgs, envListToObj(imageDetails?.Config.Env), targetStage); + const dockerfileUser = findUserStatement(dockerfile, dockerBuildArgs, envListToObj(imageDetails?.Config.Env), globalBuildxPlatformArgs, targetStage); const user = dockerfileUser || imageDetails?.Config.User || 'root'; const metadata = imageDetails ? getImageMetadata(imageDetails, substitute, output) : { config: [], raw: [], substitute }; return { @@ -477,11 +497,9 @@ export function getDevcontainerMetadataLabel(devContainerMetadata: SubstitutedCo if (!metadata.length) { return ''; } - const imageMetadataLabelValue = metadata.length !== 1 - ? `[${metadata - .map(feature => ` \\\n${toLabelString(feature)}`) - .join(',')} \\\n]` - : toLabelString(metadata[0]); + const imageMetadataLabelValue = `[${metadata + .map(feature => ` \\\n${toLabelString(feature)}`) + .join(',')} \\\n]`; return `LABEL ${imageMetadataLabel}="${imageMetadataLabelValue}"`; } diff --git a/src/spec-node/singleContainer.ts b/src/spec-node/singleContainer.ts index 07d111cc2..1c3669f74 100644 --- a/src/spec-node/singleContainer.ts +++ b/src/spec-node/singleContainer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ -import { createContainerProperties, startEventSeen, ResolverResult, getTunnelInformation, getDockerfilePath, getDockerContextPath, DockerResolverParameters, isDockerFileConfig, uriToWSLFsPath, WorkspaceConfiguration, getFolderImageName, inspectDockerImage, logUMask, SubstitutedConfig, checkDockerSupportForGPU, isBuildKitImagePolicyError } from './utils'; +import { createContainerProperties, startEventSeen, ResolverResult, getTunnelInformation, getDockerfilePath, getDockerContextPath, DockerResolverParameters, isDockerFileConfig, uriToWSLFsPath, WorkspaceConfiguration, getFolderImageName, inspectDockerImage, logUMask, SubstitutedConfig, checkDockerSupportForGPU, isBuildKitImagePolicyError, isBuildxCacheToInline } from './utils'; import { ContainerProperties, setupInContainer, ResolverProgress, ResolverParameters } from '../spec-common/injectHeadless'; import { ContainerError, toErrorText } from '../spec-common/errors'; import { ContainerDetails, listContainers, DockerCLIParameters, inspectContainers, dockerCLI, dockerPtyCLI, toPtyExecParameters, ImageDetails, toExecParameters, removeContainer } from '../spec-shutdown/dockerUtils'; @@ -20,7 +20,6 @@ export const configFileLabel = 'devcontainer.config_file'; export async function openDockerfileDevContainer(params: DockerResolverParameters, configWithRaw: SubstitutedConfig, workspaceConfig: WorkspaceConfiguration, idLabels: string[], additionalFeatures: Record>): Promise { const { common } = params; const { config } = configWithRaw; - // let collapsedFeaturesConfig: () => Promise; let container: ContainerDetails | undefined; let containerProperties: ContainerProperties | undefined; @@ -30,14 +29,7 @@ export async function openDockerfileDevContainer(params: DockerResolverParameter let imageMetadata: ImageMetadataEntry[]; let mergedConfig: MergedDevContainerConfig; if (container) { - // let _collapsedFeatureConfig: Promise; - // collapsedFeaturesConfig = async () => { - // return _collapsedFeatureConfig || (_collapsedFeatureConfig = (async () => { - // const allLabels = container?.Config.Labels || {}; - // const featuresConfig = await generateFeaturesConfig(params.common, (await createFeaturesTempFolder(params.common)), config, async () => allLabels, getContainerFeaturesFolder); - // return collapseFeaturesConfig(featuresConfig); - // })()); - // }; + await startExistingContainer(params, idLabels, container); imageMetadata = getImageMetadataFromContainer(container, configWithRaw, undefined, idLabels, common.output).config; mergedConfig = mergeConfiguration(config, imageMetadata); @@ -47,11 +39,8 @@ export async function openDockerfileDevContainer(params: DockerResolverParameter mergedConfig = mergeConfiguration(config, imageMetadata); const { containerUser } = mergedConfig; const updatedImageName = await updateRemoteUserUID(params, mergedConfig, res.updatedImageName[0], res.imageDetails, findUserArg(config.runArgs) || containerUser); - - // collapsedFeaturesConfig = async () => res.collapsedFeaturesConfig; - try { - await spawnDevContainer(params, config, mergedConfig, updatedImageName, idLabels, workspaceConfig.workspaceMount, res.imageDetails, containerUser, res.labels || {}); + await spawnDevContainer(params, config, mergedConfig, updatedImageName, idLabels, workspaceConfig.workspaceMount, workspaceConfig.additionalMountString, res.imageDetails, containerUser, res.labels || {}); } finally { // In 'finally' because 'docker run' can fail after creating the container. // Trying to get it here, so we can offer 'Rebuild Container' as an action later. @@ -209,7 +198,9 @@ async function buildAndExtendImage(buildParams: DockerResolverParameters, config if (buildParams.buildxCacheTo) { args.push('--cache-to', buildParams.buildxCacheTo); } - args.push('--build-arg', 'BUILDKIT_INLINE_CACHE=1'); + if (!isBuildxCacheToInline(buildParams.buildxCacheTo)) { + args.push('--build-arg', 'BUILDKIT_INLINE_CACHE=1'); + } } else { args.push('build'); } @@ -288,7 +279,7 @@ export function findUserArg(runArgs: string[] = []) { return runArgs[i + 1]; } if (runArg.startsWith('-u=') || runArg.startsWith('--user=')) { - return runArg.substr(runArg.indexOf('=') + 1); + return runArg.slice(runArg.indexOf('=') + 1); } } return undefined; @@ -348,7 +339,7 @@ export async function extraRunArgs(common: ResolverParameters, params: DockerRes return extraArguments; } -export async function spawnDevContainer(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, mergedConfig: MergedDevContainerConfig, imageName: string, labels: string[], workspaceMount: string | undefined, imageDetails: () => Promise, containerUser: string | undefined, extraLabels: Record) { +export async function spawnDevContainer(params: DockerResolverParameters, config: DevContainerFromDockerfileConfig | DevContainerFromImageConfig, mergedConfig: MergedDevContainerConfig, imageName: string, labels: string[], workspaceMount: string | undefined, additionalMountString: string | undefined, imageDetails: () => Promise, containerUser: string | undefined, extraLabels: Record) { const { common } = params; common.progress(ResolverProgress.StartingContainer); @@ -357,6 +348,7 @@ export async function spawnDevContainer(params: DockerResolverParameters, config const exposed = ([]).concat(...exposedPorts.map(port => ['-p', typeof port === 'number' ? `127.0.0.1:${port}:${port}` : port])); const cwdMount = workspaceMount ? ['--mount', workspaceMount] : []; + const additionalMount = additionalMountString ? ['--mount', additionalMountString] : []; const envObj = mergedConfig.containerEnv || {}; const containerEnv = Object.keys(envObj) @@ -409,6 +401,7 @@ while sleep 1 & wait $!; do :; done`, '-']; // `wait $!` allows for the `trap` t '-a', 'STDERR', ...exposed, ...cwdMount, + ...additionalMount, ...featureMounts, ...getLabels(labels), ...containerEnv, diff --git a/src/spec-node/templatesCLI/apply.ts b/src/spec-node/templatesCLI/apply.ts index ed410a8c7..0fb25c932 100644 --- a/src/spec-node/templatesCLI/apply.ts +++ b/src/spec-node/templatesCLI/apply.ts @@ -6,11 +6,12 @@ import * as jsonc from 'jsonc-parser'; import { UnpackArgv } from '../devContainersSpecCLI'; import { fetchTemplate, SelectedTemplate, TemplateFeatureOption, TemplateOptions } from '../../spec-configuration/containerTemplatesOCI'; import { runAsyncHandler } from '../utils'; +import path from 'path'; export function templateApplyOptions(y: Argv) { return y .options({ - 'workspace-folder': { type: 'string', alias: 'w', demandOption: true, default: '.', description: 'Target workspace folder to apply Template' }, + 'workspace-folder': { type: 'string', alias: 'w', description: 'Target workspace folder to apply Template. If --workspace-folder is not provided, this defaults to the current directory' }, 'template-id': { type: 'string', alias: 't', demandOption: true, description: 'Reference to a Template in a supported OCI registry' }, 'template-args': { type: 'string', alias: 'a', default: '{}', description: 'Arguments to replace within the provided Template, provided as JSON' }, 'features': { type: 'string', alias: 'f', default: '[]', description: 'Features to add to the provided Template, provided as JSON.' }, @@ -30,7 +31,7 @@ export function templateApplyHandler(args: TemplateApplyArgs) { } async function templateApply({ - 'workspace-folder': workspaceFolder, + 'workspace-folder': workspaceFolderArg, 'template-id': templateId, 'template-args': templateArgs, 'features': featuresArgs, @@ -42,6 +43,7 @@ async function templateApply({ const dispose = async () => { await Promise.all(disposables.map(d => d())); }; + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd(); const pkg = getPackageConfig(); diff --git a/src/spec-node/typings/zlib-zstd.d.ts b/src/spec-node/typings/zlib-zstd.d.ts new file mode 100644 index 000000000..6614b3eab --- /dev/null +++ b/src/spec-node/typings/zlib-zstd.d.ts @@ -0,0 +1,6 @@ +// Stub types for Zstd compression classes added in Node.js 23.8.0 +// Required for minizlib's type definitions which reference these types +declare module 'zlib' { + interface ZstdCompress extends NodeJS.ReadWriteStream {} + interface ZstdDecompress extends NodeJS.ReadWriteStream {} +} diff --git a/src/spec-node/upgradeCommand.ts b/src/spec-node/upgradeCommand.ts index 3336087c7..8773fd5de 100644 --- a/src/spec-node/upgradeCommand.ts +++ b/src/spec-node/upgradeCommand.ts @@ -23,7 +23,7 @@ import { mapNodeArchitectureToGOARCH, mapNodeOSToGOOS } from '../spec-configurat export function featuresUpgradeOptions(y: Argv) { return y .options({ - 'workspace-folder': { type: 'string', description: 'Workspace folder.', demandOption: true }, + 'workspace-folder': { type: 'string', description: 'Workspace folder. If --workspace-folder is not provided defaults to the current directory.' }, 'docker-path': { type: 'string', description: 'Path to docker executable.', default: 'docker' }, 'docker-compose-path': { type: 'string', description: 'Path to docker-compose executable.', default: 'docker-compose' }, 'config': { type: 'string', description: 'devcontainer.json path. The default is to use .devcontainer/devcontainer.json or, if that does not exist, .devcontainer.json in the workspace folder.' }, @@ -37,7 +37,6 @@ export function featuresUpgradeOptions(y: Argv) { if (argv.feature && !argv['target-version'] || !argv.feature && argv['target-version']) { throw new Error('The \'--target-version\' and \'--feature\' flag must be used together.'); } - if (argv['target-version']) { const targetVersion = argv['target-version']; if (!targetVersion.match(/^\d+(\.\d+(\.\d+)?)?$/)) { @@ -70,7 +69,7 @@ async function featuresUpgrade({ }; let output: Log | undefined; try { - const workspaceFolder = path.resolve(process.cwd(), workspaceFolderArg); + const workspaceFolder = workspaceFolderArg ? path.resolve(process.cwd(), workspaceFolderArg) : process.cwd(); const configFile = configArg ? URI.file(path.resolve(process.cwd(), configArg)) : undefined; const cliHost = await getCLIHost(workspaceFolder, loadNativeModule, true); const extensionPath = path.join(__dirname, '..', '..'); @@ -87,16 +86,18 @@ async function featuresUpgrade({ env: cliHost.env, output, }, dockerPath, dockerComposePath); + const buildPlatformInfo = { + os: mapNodeOSToGOOS(cliHost.platform), + arch: mapNodeArchitectureToGOARCH(cliHost.arch), + }; const dockerParams: DockerCLIParameters = { cliHost, dockerCLI: dockerPath, dockerComposeCLI, env: cliHost.env, output, - platformInfo: { - os: mapNodeOSToGOOS(cliHost.platform), - arch: mapNodeArchitectureToGOARCH(cliHost.arch), - } + buildPlatformInfo, + targetPlatformInfo: buildPlatformInfo, }; const workspace = workspaceFromPath(cliHost.path, workspaceFolder); @@ -137,7 +138,7 @@ async function featuresUpgrade({ const lockfilePath = getLockfilePath(config); await writeLocalFile(lockfilePath, ''); // Update lockfile - await writeLockfile(params, config, lockfile, true); + await writeLockfile(params, config, lockfile); } catch (err) { if (output) { output.write(err && (err.stack || err.message) || String(err)); @@ -193,7 +194,7 @@ function upgradeFeatureKeyInConfig(configText: string, current: string, updated: } async function getConfig(configPath: URI | undefined, cliHost: CLIHost, workspace: Workspace, output: Log, configFile: URI | undefined): Promise { - const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, true, output) || undefined; + const configs = configPath && await readDevContainerConfigFile(cliHost, workspace, configPath, true, false, output) || undefined; if (!configs) { throw new ContainerError({ description: `Dev container config (${uriToFsPath(configFile || getDefaultDevContainerConfigPath(cliHost, workspace!.configFolderPath), cliHost.platform)}) not found.` }); } diff --git a/src/spec-node/utils.ts b/src/spec-node/utils.ts index 81f6b6b17..515294e7b 100644 --- a/src/spec-node/utils.ts +++ b/src/spec-node/utils.ts @@ -15,7 +15,7 @@ import { CommonDevContainerConfig, ContainerProperties, getContainerProperties, import { Workspace } from '../spec-utils/workspaces'; import { URI } from 'vscode-uri'; import { ShellServer } from '../spec-common/shellServer'; -import { inspectContainer, inspectImage, getEvents, ContainerDetails, DockerCLIParameters, dockerExecFunction, dockerPtyCLI, dockerPtyExecFunction, toDockerImageName, DockerComposeCLI, ImageDetails, dockerCLI, removeContainer } from '../spec-shutdown/dockerUtils'; +import { inspectContainer, inspectContainers, inspectImage, getEvents, listContainers, ContainerDetails, DockerCLIParameters, dockerExecFunction, dockerPtyCLI, dockerPtyExecFunction, toDockerImageName, DockerComposeCLI, ImageDetails, dockerCLI, removeContainer } from '../spec-shutdown/dockerUtils'; import { getRemoteWorkspaceFolder } from './dockerCompose'; import { findGitRootFolder } from '../spec-common/git'; import { parentURI, uriToFsPath } from '../spec-configuration/configurationCommonUtils'; @@ -28,7 +28,6 @@ import { ImageMetadataEntry, MergedDevContainerConfig } from './imageMetadata'; import { getImageIndexEntryForPlatform, getManifest, getRef } from '../spec-configuration/containerCollectionsOCI'; import { requestEnsureAuthenticated } from '../spec-configuration/httpOCIRegistry'; import { configFileLabel, findDevContainer, hostFolderLabel } from './singleContainer'; - export { getConfigFilePath, getDockerfilePath, isDockerFileConfig } from '../spec-configuration/configuration'; export { uriToFsPath, parentURI } from '../spec-configuration/configurationCommonUtils'; @@ -46,7 +45,11 @@ export async function retry(fn: () => Promise, options: { retryIntervalMil return await fn(); } catch (err) { lastError = err; - output.write(`Retrying (Attempt ${i}) with error '${toErrorText(err)}'`, LogLevel.Warning); + output.write( + `Retrying (Attempt ${i}) with error + '${toErrorText(String(err && (err.stack || err.message) || err))}'`, + LogLevel.Warning + ); await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds)); } } @@ -90,6 +93,13 @@ export async function logUMask(params: DockerResolverParameters): Promise dockerPtyCLI(params, 'pull', imageName), { maxRetries: 5, retryIntervalMilliseconds: 1000, output }); - } catch (_err) { - if (err.stdout) { - output.write(err.stdout.toString()); - } - if (err.stderr) { - output.write(toErrorText(err.stderr.toString())); - } - throw err; + } catch (pullErr) { + logErrorStdoutStderr(inspectErr, output); + logErrorStdoutStderr(pullErr, output); + throw pullErr; } - return inspectImage(params, imageName); + try { + return await inspectImage(params, imageName); + } catch (inspectErr3) { + logErrorStdoutStderr(inspectErr3, output); + throw inspectErr3; + } + } +} + +function logErrorStdoutStderr(err: any, output: Log) { + if (err?.message) { + output.write(err.message, LogLevel.Error); + } + if (err?.stdout) { + output.write(err.stdout.toString(), LogLevel.Error); + } + if (err?.stderr) { + output.write(toErrorText(err.stderr.toString()), LogLevel.Error); } } @@ -343,24 +370,58 @@ export async function getHostMountFolder(cliHost: CLIHost, folderPath: string, m export interface WorkspaceConfiguration { workspaceMount: string | undefined; workspaceFolder: string | undefined; + additionalMountString: string | undefined; } -export async function getWorkspaceConfiguration(cliHost: CLIHost, workspace: Workspace | undefined, config: DevContainerConfig, mountWorkspaceGitRoot: boolean, output: Log, consistency?: BindMountConsistency): Promise { +export async function getWorkspaceConfiguration(cliHost: CLIHost, workspace: Workspace | undefined, config: DevContainerConfig, mountWorkspaceGitRoot: boolean, mountGitWorktreeCommonDir: boolean, output: Log, consistency?: BindMountConsistency): Promise { if ('dockerComposeFile' in config) { return { workspaceFolder: getRemoteWorkspaceFolder(config), workspaceMount: undefined, + additionalMountString: undefined, }; } let { workspaceFolder, workspaceMount } = config; + let additionalMountString: string | undefined; if (workspace && (!workspaceFolder || !('workspaceMount' in config))) { const hostMountFolder = await getHostMountFolder(cliHost, workspace.rootFolderPath, mountWorkspaceGitRoot, output); + + // Check if .git is a file (worktree) with a relative gitdir path + let containerMountFolder = path.posix.join('/workspaces', cliHost.path.basename(hostMountFolder)); + if (mountWorkspaceGitRoot && mountGitWorktreeCommonDir) { + const dotGitPath = cliHost.path.join(hostMountFolder, '.git'); + if (await cliHost.isFile(dotGitPath)) { + const dotGitContent = (await cliHost.readFile(dotGitPath)).toString(); + const match = /^gitdir:\s*(.+)$/m.exec(dotGitContent); + if (match) { + const gitdir = match[1]; + // Only handle if gitdir is a relative path + if (!cliHost.path.isAbsolute(gitdir)) { + // gitdir points to .git/worktrees//, common dir is .git/ (two levels up) + const gitCommonDir = cliHost.path.resolve(hostMountFolder, gitdir, '..', '..'); + // Collect path segments from hostMountFolder up to the parent of gitCommonDir + const segments: string[] = []; + for (let current = hostMountFolder; !gitCommonDir.startsWith(current + cliHost.path.sep) && current !== cliHost.path.dirname(current); current = cliHost.path.dirname(current)) { + segments.unshift(cliHost.path.basename(current)); + } + containerMountFolder = path.posix.join('/workspaces', ...segments); + // Calculate where the common dir should be mounted in the container + const containerGitdir = cliHost.platform === 'win32' ? gitdir.replace(/\\/g, '/') : gitdir; + const containerGitCommonDir = path.posix.resolve(containerMountFolder, containerGitdir, '..', '..'); + const cons = cliHost.platform !== 'linux' ? `,consistency=${consistency || 'consistent'}` : ''; + const srcQuote = gitCommonDir.indexOf(',') !== -1 ? '"' : ''; + const tgtQuote = containerGitCommonDir.indexOf(',') !== -1 ? '"' : ''; + additionalMountString = `type=bind,${srcQuote}source=${gitCommonDir}${srcQuote},${tgtQuote}target=${containerGitCommonDir}${tgtQuote}${cons}`; + } + } + } + } + if (!workspaceFolder) { - const rel = cliHost.path.relative(cliHost.path.dirname(hostMountFolder), workspace.rootFolderPath); - workspaceFolder = `/workspaces/${cliHost.platform === 'win32' ? rel.replace(/\\/g, '/') : rel}`; + const rel = cliHost.path.relative(hostMountFolder, workspace.rootFolderPath); + workspaceFolder = path.posix.join(containerMountFolder, cliHost.platform === 'win32' ? rel.replace(/\\/g, '/') : rel); } if (!('workspaceMount' in config)) { - const containerMountFolder = `/workspaces/${cliHost.path.basename(hostMountFolder)}`; const cons = cliHost.platform !== 'linux' ? `,consistency=${consistency || 'consistent'}` : ''; // Podman does not tolerate consistency= const srcQuote = hostMountFolder.indexOf(',') !== -1 ? '"' : ''; const tgtQuote = containerMountFolder.indexOf(',') !== -1 ? '"' : ''; @@ -370,6 +431,7 @@ export async function getWorkspaceConfiguration(cliHost: CLIHost, workspace: Wor return { workspaceFolder, workspaceMount, + additionalMountString, }; } @@ -552,6 +614,71 @@ export function getEmptyContextFolder(common: ResolverParameters) { return common.cliHost.path.join(common.persistedFolder, 'empty-folder'); } +export function normalizeDevContainerLabelPath(platform: NodeJS.Platform, value: string): string { + if (platform !== 'win32') { + return value; + } + + // Normalize separators and dot segments, then explicitly lowercase the drive + // letter because devcontainer.local_folder / devcontainer.config_file labels + // should compare case-insensitively on Windows. + const normalized = path.win32.normalize(value); + if (normalized.length >= 2 && normalized[1] === ':') { + return normalized[0].toLowerCase() + normalized.slice(1); + } + + return normalized; +} + +async function findDevContainerByNormalizedLabels(params: DockerResolverParameters | DockerCLIParameters, normalizedWorkspaceFolder: string, normalizedConfigFile: string) { + if (process.platform !== 'win32') { + return undefined; + } + + const ids = await listContainers(params, true, [hostFolderLabel]); + if (!ids.length) { + return undefined; + } + + const details = await inspectContainers(params, ids); + return details + .filter(container => container.State.Status !== 'removing') + .find(container => { + const labels = container.Config.Labels || {}; + const containerWorkspaceFolder = labels[hostFolderLabel]; + const normalizedContainerWorkspaceFolder = containerWorkspaceFolder && normalizeDevContainerLabelPath('win32', containerWorkspaceFolder); + if (!normalizedContainerWorkspaceFolder || normalizedContainerWorkspaceFolder !== normalizedWorkspaceFolder) { + return false; + } + + const containerConfigFile = labels[configFileLabel]; + const normalizedContainerConfigFile = containerConfigFile && normalizeDevContainerLabelPath('win32', containerConfigFile); + return !!normalizedContainerConfigFile + && normalizedContainerConfigFile === normalizedConfigFile; + }); +} + +async function findLegacyDevContainerByNormalizedWorkspaceFolder(params: DockerResolverParameters | DockerCLIParameters, normalizedWorkspaceFolder: string) { + if (process.platform !== 'win32') { + return undefined; + } + + const ids = await listContainers(params, true, [hostFolderLabel]); + if (!ids.length) { + return undefined; + } + + const details = await inspectContainers(params, ids); + return details + .filter(container => container.State.Status !== 'removing') + .find(container => { + const labels = container.Config.Labels || {}; + const containerWorkspaceFolder = labels[hostFolderLabel]; + const normalizedContainerWorkspaceFolder = containerWorkspaceFolder && normalizeDevContainerLabelPath('win32', containerWorkspaceFolder); + return normalizedContainerWorkspaceFolder === normalizedWorkspaceFolder; + }); +} + export async function findContainerAndIdLabels(params: DockerResolverParameters | DockerCLIParameters, containerId: string | undefined, providedIdLabels: string[] | undefined, workspaceFolder: string | undefined, configFile: string | undefined, removeContainerWithOldLabels?: boolean | string) { if (providedIdLabels) { return { @@ -559,14 +686,26 @@ export async function findContainerAndIdLabels(params: DockerResolverParameters idLabels: providedIdLabels, }; } + + const normalizedWorkspaceFolder = workspaceFolder ? normalizeDevContainerLabelPath(process.platform, workspaceFolder) : workspaceFolder; + const normalizedConfigFile = configFile ? normalizeDevContainerLabelPath(process.platform, configFile) : configFile; + const oldLabels = [`${hostFolderLabel}=${normalizedWorkspaceFolder}`]; + const newLabels = [...oldLabels, `${configFileLabel}=${normalizedConfigFile}`]; + let container: ContainerDetails | undefined; if (containerId) { container = await inspectContainer(params, containerId); - } else if (workspaceFolder && configFile) { - container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`]); + } else if (normalizedWorkspaceFolder && normalizedConfigFile) { + container = await findDevContainer(params, newLabels); + if (!container) { + container = await findDevContainerByNormalizedLabels(params, normalizedWorkspaceFolder, normalizedConfigFile); + } if (!container) { // Fall back to old labels. - container = await findDevContainer(params, [`${hostFolderLabel}=${workspaceFolder}`]); + container = await findDevContainer(params, oldLabels); + if (!container) { + container = await findLegacyDevContainerByNormalizedWorkspaceFolder(params, normalizedWorkspaceFolder); + } if (container) { if (container.Config.Labels?.[configFileLabel]) { // But ignore containers with new labels. @@ -583,9 +722,7 @@ export async function findContainerAndIdLabels(params: DockerResolverParameters } return { container, - idLabels: !container || container.Config.Labels?.[configFileLabel] ? - [`${hostFolderLabel}=${workspaceFolder}`, `${configFileLabel}=${configFile}`] : - [`${hostFolderLabel}=${workspaceFolder}`], + idLabels: !container || container.Config.Labels?.[configFileLabel] ? newLabels : oldLabels, }; } diff --git a/src/spec-shutdown/dockerUtils.ts b/src/spec-shutdown/dockerUtils.ts index 10205c420..9f0bce850 100644 --- a/src/spec-shutdown/dockerUtils.ts +++ b/src/spec-shutdown/dockerUtils.ts @@ -52,7 +52,8 @@ export interface DockerCLIParameters { dockerComposeCLI: () => Promise; env: NodeJS.ProcessEnv; output: Log; - platformInfo: PlatformInfo; + buildPlatformInfo: PlatformInfo; + targetPlatformInfo: PlatformInfo; } export interface PartialExecParameters { @@ -259,6 +260,24 @@ export async function dockerBuildKitVersion(params: DockerCLIParameters | Partia } } +export async function dockerEngineVersion(params: DockerCLIParameters | PartialExecParameters | DockerResolverParameters): Promise<{ versionString: string; versionMatch?: string } | undefined> { + try { + const execParams = { + ...toExecParameters(params), + print: true, + }; + const result = await dockerCLI(execParams, 'version', '--format', '{{.Server.Version}}'); + const versionString = result.stdout.toString().trim(); + const versionMatch = versionString.match(/(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)/); + if (!versionMatch) { + return { versionString }; + } + return { versionString, versionMatch: versionMatch[0] }; + } catch { + return undefined; + } +} + export async function dockerCLI(params: DockerCLIParameters | PartialExecParameters | DockerResolverParameters, ...args: string[]) { const partial = toExecParameters(params); return runCommandNoPty({ diff --git a/src/spec-utils/httpRequest.ts b/src/spec-utils/httpRequest.ts index 43df09d78..162c55cc5 100644 --- a/src/spec-utils/httpRequest.ts +++ b/src/spec-utils/httpRequest.ts @@ -87,7 +87,7 @@ export async function requestResolveHeaders(options: { type: string; url: string const parsed = new url.URL(options.url); const reqOptions: RequestOptions & tls.CommonConnectionOptions & FollowOptions = { hostname: parsed.hostname, - maxBodyLength: 100 * 1024 * 1024, + maxBodyLength: Infinity, port: parsed.port, path: parsed.pathname + parsed.search, method: options.type, diff --git a/src/test/cli.build.test.ts b/src/test/cli.build.test.ts index 0dfae0427..e2980ea19 100644 --- a/src/test/cli.build.test.ts +++ b/src/test/cli.build.test.ts @@ -14,7 +14,7 @@ import { envListToObj } from '../spec-node/utils'; const pkg = require('../../package.json'); describe('Dev Containers CLI', function () { - this.timeout('120s'); + this.timeout('240s'); const tmp = path.relative(process.cwd(), path.join(__dirname, 'tmp')); const cli = `npx --prefix ${tmp} devcontainer`; @@ -433,5 +433,49 @@ describe('Dev Containers CLI', function () { const details = JSON.parse((await shellExec(`docker inspect ${response.imageName}`)).stdout)[0] as ImageDetails; assert.strictEqual(details.Config.Labels?.test_build_options, 'success'); }); + + it('should use current directory for build when no workspace-folder provided', async function () { + const testFolder = `${__dirname}/configs/image`; + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + console.log(`Original cwd: ${originalCwd}`); + console.log(`Changing to test folder: ${testFolder}`); + try { + process.chdir(testFolder); + const res = await shellExec(`${absoluteCli} build`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + assert.ok(response.imageName); + } finally { + process.chdir(originalCwd); + } + }); + + it('should fail gracefully when no workspace-folder and no config in current directory', async function () { + const tempDir = path.join(os.tmpdir(), 'devcontainer-build-test-' + Date.now()); + await shellExec(`mkdir -p ${tempDir}`); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + try { + process.chdir(tempDir); + let success = false; + try { + await shellExec(`${absoluteCli} build`); + success = true; + } catch (error) { + assert.equal(error.error.code, 1, 'Should fail with exit code 1'); + const res = JSON.parse(error.stdout); + assert.equal(res.outcome, 'error'); + assert.match(res.message, /Dev container config .* not found/); + } + assert.equal(success, false, 'expect non-successful call'); + } finally { + process.chdir(originalCwd); + await shellExec(`rm -rf ${tempDir}`); + } + }); + }); }); diff --git a/src/test/cli.exec.base.ts b/src/test/cli.exec.base.ts index 10e876595..681aac775 100644 --- a/src/test/cli.exec.base.ts +++ b/src/test/cli.exec.base.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as os from 'os'; import { BuildKitOption, commandMarkerTests, devContainerDown, devContainerStop, devContainerUp, pathExists, shellBufferExec, shellExec, shellPtyExec } from './testUtils'; const pkg = require('../../package.json'); @@ -82,6 +83,22 @@ export function describeTests1({ text, options }: BuildKitOption) { assert.strictEqual(env.FOO, 'BAR'); assert.strictEqual(env.BAZ, ''); }); + it('should exec with default workspace folder (current directory)', async () => { + const originalCwd = process.cwd(); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + process.chdir(testFolder); + + try { + // Exec without --workspace-folder should use current directory as default + const execRes = await shellExec(`${absoluteCli} exec echo "default workspace test"`); + assert.strictEqual(execRes.error, null); + assert.match(execRes.stdout, /default workspace test/); + } finally { + // Restore original directory + process.chdir(originalCwd); + } + }); }); describe(`with valid (image) config containing features [${text}]`, () => { let containerId: string | null = null; @@ -406,6 +423,60 @@ export function describeTests2({ text, options }: BuildKitOption) { await shellExec(`docker rm -f ${response.containerId}`); }); + + describe('Command exec with default workspace', () => { + it('should fail gracefully when no config in current directory and no container-id', async () => { + const tempDir = path.join(os.tmpdir(), 'devcontainer-exec-test-' + Date.now()); + await shellExec(`mkdir -p ${tempDir}`); + const originalCwd = process.cwd(); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + try { + process.chdir(tempDir); + let success = false; + try { + // Test exec without --workspace-folder (should default to current directory with no config) + await shellExec(`${absoluteCli} exec echo "test"`); + success = true; + } catch (error) { + console.log('Caught error as expected: ', error.stderr); + // Should fail because there's no container or config + assert.equal(error.error.code, 1, 'Should fail with exit code 1'); + } + assert.equal(success, false, 'expect non-successful call'); + } finally { + process.chdir(originalCwd); + await shellExec(`rm -rf ${tempDir}`); + } + }); + + describe('with valid config in current directory', () => { + let containerId: string | null = null; + const testFolder = `${__dirname}/configs/image`; + + beforeEach(async () => { + containerId = (await devContainerUp(cli, testFolder, options)).containerId; + }); + + afterEach(async () => await devContainerDown({ containerId })); + + it('should execute command successfully when using current directory', async () => { + const originalCwd = process.cwd(); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + try { + process.chdir(testFolder); + // Test exec without --workspace-folder (should default to current directory) + const res = await shellExec(`${absoluteCli} exec echo "hello world"`); + assert.strictEqual(res.error, null); + assert.match(res.stdout, /hello world/); + } finally { + process.chdir(originalCwd); + } + }); + }); + + }); }); }); } diff --git a/src/test/cli.test.ts b/src/test/cli.test.ts index 522c9073d..f409cb0fd 100644 --- a/src/test/cli.test.ts +++ b/src/test/cli.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as path from 'path'; +import * as os from 'os'; import { devContainerDown, devContainerUp, shellExec } from './testUtils'; const pkg = require('../../package.json'); @@ -68,6 +69,59 @@ describe('Dev Containers CLI', function () { await shellExec(`docker rm -f ${upResponse.containerId}`); }); + + it('run-user-commands should run with default workspace folder (current directory)', async () => { + const testFolder = `${__dirname}/configs/image`; + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + + // First, ensure container is up + const upRes = await shellExec(`${cli} up --workspace-folder ${testFolder} --skip-post-create`); + const upResponse = JSON.parse(upRes.stdout); + assert.strictEqual(upResponse.outcome, 'success'); + const containerId = upResponse.containerId; + + const originalCwd = process.cwd(); + try { + // Change to workspace folder + process.chdir(testFolder); + + // Run user commands without --workspace-folder should use current directory as default + const runRes = await shellExec(`${absoluteCli} run-user-commands`); + const runResponse = JSON.parse(runRes.stdout); + assert.strictEqual(runResponse.outcome, 'success'); + + // Verify that the postCreateCommand was executed + await shellExec(`docker exec ${containerId} test -f /postCreateCommand.txt`); + } finally { + // Restore original directory + process.chdir(originalCwd); + // Clean up container + await shellExec(`docker rm -f ${containerId}`); + } + }); + + it('run-user-commands should fail gracefully when no config in current directory and no container-id', async () => { + const tempDir = path.join(os.tmpdir(), 'devcontainer-run-test-' + Date.now()); + await shellExec(`mkdir -p ${tempDir}`); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + try { + process.chdir(tempDir); + let success = false; + try { + await shellExec(`${absoluteCli} run-user-commands`); + success = true; + } catch (error) { + assert.equal(error.error.code, 1, 'Should fail with exit code 1'); + } + assert.equal(success, false, 'expect non-successful call'); + } finally { + process.chdir(originalCwd); + await shellExec(`rm -rf ${tempDir}`); + } + }); }); describe('Command read-configuration', () => { @@ -124,5 +178,42 @@ describe('Dev Containers CLI', function () { const response = JSON.parse(res.stdout); assert.strictEqual(response.configuration.remoteEnv.SUBFOLDER_CONFIG_REMOTE_ENV, 'true'); }); + + it('should use current directory for read-configuration when no workspace-folder provided', async () => { + const testFolder = `${__dirname}/configs/image`; + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + try { + process.chdir(testFolder); + const res = await shellExec(`${absoluteCli} read-configuration`); + const response = JSON.parse(res.stdout); + assert.equal(response.configuration.image, 'ubuntu:latest'); + } finally { + process.chdir(originalCwd); + } + }); + + it('should fail gracefully when no workspace-folder and no config in current directory', async () => { + const tempDir = path.join(os.tmpdir(), 'devcontainer-test-' + Date.now()); + await shellExec(`mkdir -p ${tempDir}`); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + try { + process.chdir(tempDir); + let success = false; + try { + await shellExec(`${absoluteCli} read-configuration`); + success = true; + } catch (error) { + assert.equal(error.error.code, 1, 'Should fail with exit code 1'); + } + assert.equal(success, false, 'expect non-successful call'); + } finally { + process.chdir(originalCwd); + await shellExec(`rm -rf ${tempDir}`); + } + }); }); }); \ No newline at end of file diff --git a/src/test/cli.up.test.ts b/src/test/cli.up.test.ts index 94515e89a..688a4c84d 100644 --- a/src/test/cli.up.test.ts +++ b/src/test/cli.up.test.ts @@ -310,4 +310,52 @@ describe('Dev Containers CLI', function () { await shellExec(`docker rm -f ${response.containerId}`); }); }); + + describe('Command up with default workspace', () => { + it('should create and start container using current directory config', async () => { + const testFolder = `${__dirname}/configs/image`; + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + let containerId: string | null = null; + try { + process.chdir(testFolder); + const res = await shellExec(`${absoluteCli} up`); + const response = JSON.parse(res.stdout); + containerId = response.containerId; + assert.equal(response.outcome, 'success'); + assert.ok(containerId); + } finally { + process.chdir(originalCwd); + if (containerId) { + await shellExec(`docker rm -f ${containerId}`); + } + } + }); + + it('should fail gracefully when no config in current directory', async () => { + const tempDir = path.join(os.tmpdir(), 'devcontainer-up-test-' + Date.now()); + await shellExec(`mkdir -p ${tempDir}`); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + const originalCwd = process.cwd(); + try { + process.chdir(tempDir); + let success = false; + try { + await shellExec(`${absoluteCli} up`); + success = true; + } catch (error) { + assert.equal(error.error.code, 1, 'Should fail with exit code 1'); + const res = JSON.parse(error.stdout); + assert.equal(res.outcome, 'error'); + assert.match(res.message, /Dev container config .* not found/); + } + assert.equal(success, false, 'expect non-successful call'); + } finally { + process.chdir(originalCwd); + await shellExec(`rm -rf ${tempDir}`); + } + }); + }); }); \ No newline at end of file diff --git a/src/test/configs/.gitignore b/src/test/configs/.gitignore new file mode 100644 index 000000000..58dacec39 --- /dev/null +++ b/src/test/configs/.gitignore @@ -0,0 +1,6 @@ +# Lock files will be generated during test runs. Ignore them from git because +# they can point to different hashes when the feature is updated. +# To add a lock file when it is required for a test, add it to git with the command: +# git add --force devcontainer-lock.json +devcontainer-lock.json +.devcontainer-lock.json diff --git a/src/test/configs/example/.devcontainer.json b/src/test/configs/example/.devcontainer.json index b7e3ab7a0..5f96e6330 100644 --- a/src/test/configs/example/.devcontainer.json +++ b/src/test/configs/example/.devcontainer.json @@ -3,7 +3,7 @@ { "image": "mcr.microsoft.com/devcontainers/base:latest", "features": { - "ghcr.io/devcontainers/features/go:1": { + "ghcr.io/devcontainers/features/github-cli:1": { "version": "latest" } } diff --git a/src/test/container-features/configs/.gitignore b/src/test/container-features/configs/.gitignore new file mode 100644 index 000000000..58dacec39 --- /dev/null +++ b/src/test/container-features/configs/.gitignore @@ -0,0 +1,6 @@ +# Lock files will be generated during test runs. Ignore them from git because +# they can point to different hashes when the feature is updated. +# To add a lock file when it is required for a test, add it to git with the command: +# git add --force devcontainer-lock.json +devcontainer-lock.json +.devcontainer-lock.json diff --git a/src/test/container-features/configs/lockfile-dependson/expected.devcontainer-lock.json b/src/test/container-features/configs/lockfile-dependson/expected.devcontainer-lock.json index a3ed4b5d2..90f784d43 100644 --- a/src/test/container-features/configs/lockfile-dependson/expected.devcontainer-lock.json +++ b/src/test/container-features/configs/lockfile-dependson/expected.devcontainer-lock.json @@ -27,4 +27,4 @@ ] } } -} \ No newline at end of file +} diff --git a/src/test/container-features/configs/lockfile-frozen-no-lockfile/.devcontainer.json b/src/test/container-features/configs/lockfile-frozen-no-lockfile/.devcontainer.json new file mode 100644 index 000000000..d98d20705 --- /dev/null +++ b/src/test/container-features/configs/lockfile-frozen-no-lockfile/.devcontainer.json @@ -0,0 +1,7 @@ +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/codspace/features/flower:1": {}, + "ghcr.io/codspace/features/color:1": {} + } +} diff --git a/src/test/container-features/configs/lockfile-frozen/.devcontainer-lock.json b/src/test/container-features/configs/lockfile-frozen/.devcontainer-lock.json index 4d9bc604e..1e1764123 100644 --- a/src/test/container-features/configs/lockfile-frozen/.devcontainer-lock.json +++ b/src/test/container-features/configs/lockfile-frozen/.devcontainer-lock.json @@ -11,4 +11,4 @@ "integrity": "sha256:c9cc1ac636b9ef595512b5ca7ecb3a35b7d3499cb6f86372edec76ae0cd71d43" } } -} \ No newline at end of file +} diff --git a/src/test/container-features/configs/lockfile-no-lockfile/.devcontainer.json b/src/test/container-features/configs/lockfile-no-lockfile/.devcontainer.json new file mode 100644 index 000000000..e54a9012f --- /dev/null +++ b/src/test/container-features/configs/lockfile-no-lockfile/.devcontainer.json @@ -0,0 +1,7 @@ +{ + "image": "mcr.microsoft.com/devcontainers/base:ubuntu", + "features": { + "ghcr.io/codspace/features/flower:1.0.0": {}, + "ghcr.io/codspace/features/color:1.0.4": {} + } +} diff --git a/src/test/container-features/configs/lockfile-outdated/expected.devcontainer-lock.json b/src/test/container-features/configs/lockfile-outdated/expected.devcontainer-lock.json index d4491d4dc..8d2fa74e0 100644 --- a/src/test/container-features/configs/lockfile-outdated/expected.devcontainer-lock.json +++ b/src/test/container-features/configs/lockfile-outdated/expected.devcontainer-lock.json @@ -11,4 +11,4 @@ "integrity": "sha256:c9cc1ac636b9ef595512b5ca7ecb3a35b7d3499cb6f86372edec76ae0cd71d43" } } -} \ No newline at end of file +} diff --git a/src/test/container-features/configs/lockfile-upgrade-command/upgraded.devcontainer-lock.json b/src/test/container-features/configs/lockfile-upgrade-command/upgraded.devcontainer-lock.json index bb0b7103b..f3916df75 100644 --- a/src/test/container-features/configs/lockfile-upgrade-command/upgraded.devcontainer-lock.json +++ b/src/test/container-features/configs/lockfile-upgrade-command/upgraded.devcontainer-lock.json @@ -21,4 +21,4 @@ "integrity": "sha256:9024deeca80347dea7603a3bb5b4951988f0bf5894ba036a6ee3f29c025692c6" } } -} \ No newline at end of file +} diff --git a/src/test/container-features/configs/lockfile/expected.devcontainer-lock.json b/src/test/container-features/configs/lockfile/expected.devcontainer-lock.json index dd3136f9d..8e85b136b 100644 --- a/src/test/container-features/configs/lockfile/expected.devcontainer-lock.json +++ b/src/test/container-features/configs/lockfile/expected.devcontainer-lock.json @@ -16,4 +16,4 @@ "integrity": "sha256:41607bd6aba3975adcd0641cc479e67b04abd21763ba8a41ea053bcc04a6a818" } } -} \ No newline at end of file +} diff --git a/src/test/container-features/containerFeaturesOCI.test.ts b/src/test/container-features/containerFeaturesOCI.test.ts index 52e6fde10..9281a7498 100644 --- a/src/test/container-features/containerFeaturesOCI.test.ts +++ b/src/test/container-features/containerFeaturesOCI.test.ts @@ -255,7 +255,9 @@ describe('getRef()', async function () { }); describe('Test OCI Pull', async function () { - this.timeout('10s'); + // These tests fetch manifests/blobs from a live OCI registry, so allow + // extra time for auth/token exchange and transient network latency in CI. + this.timeout('60s'); it('Parse OCI identifier', async function () { const feat = getRef(output, 'ghcr.io/codspace/features/ruby:1'); diff --git a/src/test/container-features/containerFeaturesOCIPush.test.ts b/src/test/container-features/containerFeaturesOCIPush.test.ts index 76d743e65..b6672ffc9 100644 --- a/src/test/container-features/containerFeaturesOCIPush.test.ts +++ b/src/test/container-features/containerFeaturesOCIPush.test.ts @@ -307,7 +307,7 @@ registry`; // NOTE: // Test depends on https://github.com/orgs/codspace/packages/container/non-empty-config-layer%2Fcolor/225254837?tag=1.0.0 describe('Test OCI Push Helper Functions', function () { - this.timeout('10s'); + this.timeout('20s'); it('Generates the correct tgz manifest layer', async () => { const dataBytes = fs.readFileSync(`${testAssetsDir}/devcontainer-feature-color.tgz`); diff --git a/src/test/container-features/containerFeaturesOrder.test.ts b/src/test/container-features/containerFeaturesOrder.test.ts index e8b2cfdd9..8426a0946 100644 --- a/src/test/container-features/containerFeaturesOrder.test.ts +++ b/src/test/container-features/containerFeaturesOrder.test.ts @@ -47,7 +47,7 @@ async function setupInstallOrderTest(testWorkspaceFolder: string) { } describe('Feature Dependencies', function () { - this.timeout('10s'); + this.timeout('20s'); const baseTestConfigPath = `${__dirname}/configs/feature-dependencies`; describe('installsAfter', function () { diff --git a/src/test/container-features/featureHelpers.test.ts b/src/test/container-features/featureHelpers.test.ts index fb3c079ed..3e08c0648 100644 --- a/src/test/container-features/featureHelpers.test.ts +++ b/src/test/container-features/featureHelpers.test.ts @@ -57,6 +57,9 @@ describe('validate processFeatureIdentifier', async function () { console.log(`workspaceRoot = ${workspaceRoot}, defaultConfigPath = ${defaultConfigPath}`); describe('VALID processFeatureIdentifier examples', async function () { + // These cases perform live OCI/GHCR requests, so allow extra time for + // registry auth/token exchange and transient network latency in CI. + this.timeout('20s'); it('should process v1 local-cache', async function () { // Parsed out of a user's devcontainer.json diff --git a/src/test/container-features/featuresCLICommands.test.ts b/src/test/container-features/featuresCLICommands.test.ts index 1d20d5b1a..2dd2bd0e8 100644 --- a/src/test/container-features/featuresCLICommands.test.ts +++ b/src/test/container-features/featuresCLICommands.test.ts @@ -1,15 +1,15 @@ import { assert } from 'chai'; import path from 'path'; +import { existsSync } from 'fs'; import { createPlainLog, LogLevel, makeLog } from '../../spec-utils/log'; import { isLocalFile, readLocalFile } from '../../spec-utils/pfs'; import { ExecResult, shellExec } from '../testUtils'; import { getSemanticTags } from '../../spec-node/collectionCommonUtils/publishCommandImpl'; import { getRef, getPublishedTags, getVersionsStrictSorted } from '../../spec-configuration/containerCollectionsOCI'; import { generateFeaturesDocumentation } from '../../spec-node/collectionCommonUtils/generateDocsCommandImpl'; +import pkg from '../../../package.json'; export const output = makeLog(createPlainLog(text => process.stdout.write(text), () => LogLevel.Trace)); -const pkg = require('../../../package.json'); - describe('CLI features subcommands', async function () { this.timeout('240s'); @@ -425,6 +425,119 @@ describe('CLI features subcommands', async function () { }); }); + describe('features resolve-dependencies', function () { + + it('should resolve dependencies when workspace-folder defaults to current directory', async function () { + // Create a test config with features that have dependencies + const testConfigPath = path.resolve(__dirname, 'configs/feature-dependencies/dependsOn/oci-ab'); + const originalCwd = process.cwd(); + + try { + // Change to test config directory to test default workspace folder behavior + process.chdir(testConfigPath); + + // Use absolute path to CLI to prevent npm ENOENT errors + const absoluteTmpPath = path.resolve(originalCwd, tmp); + const absoluteCliPath = `npx --prefix ${absoluteTmpPath} devcontainer`; + + // First check if the config file exists + const configExists = existsSync('.devcontainer/devcontainer.json') || + existsSync('.devcontainer.json'); + assert.isTrue(configExists, 'Test config file should exist'); + + let result; + try { + result = await shellExec(`${absoluteCliPath} features resolve-dependencies --log-level trace`); + } catch (error: any) { + // If command fails, log details for debugging + console.error('Command failed:', error); + if (error.stderr) { + console.error('STDERR:', error.stderr); + } + if (error.stdout) { + console.error('STDOUT:', error.stdout); + } + throw error; + } + + // Verify the command succeeded + assert.isDefined(result); + assert.isString(result.stdout); + assert.isNotEmpty(result.stdout.trim(), 'Command should produce output'); + + // Parse the JSON output to verify it contains expected structure + let jsonOutput; + try { + // Try parsing stdout directly first + jsonOutput = JSON.parse(result.stdout.trim()); + } catch (parseError) { + // If direct parsing fails, try extracting JSON from mixed output + const lines = result.stdout.split('\n'); + + // Find the last occurrence of '{' that starts a complete JSON object + let jsonStartIndex = -1; + let jsonEndIndex = -1; + let braceCount = 0; + + // Work backwards from the end to find the complete JSON + for (let i = lines.length - 1; i >= 0; i--) { + const line = lines[i].trim(); + if (line === '}' && jsonEndIndex === -1) { + jsonEndIndex = i; + braceCount = 1; + } else if (jsonEndIndex !== -1) { + // Count braces to find matching opening + for (const char of line) { + if (char === '}') { + braceCount++; + } else if (char === '{') { + braceCount--; + } + } + if (braceCount === 0 && line === '{') { + jsonStartIndex = i; + break; + } + } + } + + if (jsonStartIndex >= 0 && jsonEndIndex >= 0) { + // Extract just the JSON lines + const jsonLines = lines.slice(jsonStartIndex, jsonEndIndex + 1); + const jsonString = jsonLines.join('\n'); + try { + jsonOutput = JSON.parse(jsonString); + } catch (innerError) { + console.error('Failed to parse extracted JSON:', jsonString.substring(0, 500) + '...'); + throw new Error(`Failed to parse extracted JSON: ${innerError}`); + } + } else { + console.error('Could not find complete JSON in output'); + console.error('Last 10 lines:', lines.slice(-10)); + throw new Error(`Failed to find complete JSON in output: ${parseError}`); + } + } + + assert.isDefined(jsonOutput, 'Should have valid JSON output'); + assert.property(jsonOutput, 'installOrder'); + assert.isArray(jsonOutput.installOrder); + + // Verify the install order contains the expected features + const installOrder = jsonOutput.installOrder; + assert.isAbove(installOrder.length, 0, 'Install order should contain at least one feature'); + + // Each item should have id and options + installOrder.forEach((item: any) => { + assert.property(item, 'id'); + assert.property(item, 'options'); + }); + + } finally { + process.chdir(originalCwd); + } + }); + }); + describe('features package', function () { it('features package subcommand by collection', async function () { diff --git a/src/test/container-features/generateFeaturesConfig.test.ts b/src/test/container-features/generateFeaturesConfig.test.ts index 32647a71c..915c3e2da 100644 --- a/src/test/container-features/generateFeaturesConfig.test.ts +++ b/src/test/container-features/generateFeaturesConfig.test.ts @@ -21,7 +21,7 @@ describe('validate generateFeaturesConfig()', function () { const env = { 'SOME_KEY': 'SOME_VAL' }; const platform = process.platform; const cacheFolder = path.join(os.tmpdir(), `devcontainercli-test-${crypto.randomUUID()}`); - const params = { extensionPath: '', cwd: '', output, env, cacheFolder, persistedFolder: '', skipFeatureAutoMapping: false, platform }; + const params = { extensionPath: '', cwd: '', output, env, cacheFolder, persistedFolder: '', skipFeatureAutoMapping: false, platform, noLockfile: true }; it('should correctly return a featuresConfig with v2 local features', async function () { const version = 'unittest'; @@ -88,7 +88,7 @@ RUN chmod -R 0755 /tmp/dev-container-features/hello_1 \\ }); it('should correctly return featuresConfig with customizations', async function () { - this.timeout('20s'); + this.timeout('40s'); const version = 'unittest'; const tmpFolder: string = path.join(await getLocalCacheFolder(), 'container-features', `${version}-${Date.now()}`); await mkdirpLocal(tmpFolder); diff --git a/src/test/container-features/generateLockfile.test.ts b/src/test/container-features/generateLockfile.test.ts new file mode 100644 index 000000000..4737a59a4 --- /dev/null +++ b/src/test/container-features/generateLockfile.test.ts @@ -0,0 +1,172 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from 'chai'; +import { URI } from 'vscode-uri'; +import { DevContainerConfig } from '../../spec-configuration/configuration'; +import { + DirectTarballSourceInformation, + FeatureSet, + FeaturesConfig, + OCISourceInformation, +} from '../../spec-configuration/containerFeaturesConfiguration'; +import { generateLockfile } from '../../spec-configuration/lockfile'; + +function makeOciFeatureSet(userFeatureId: string, version: string, digest: string): FeatureSet { + const sourceInformation: OCISourceInformation = { + type: 'oci', + userFeatureId, + userFeatureIdWithoutVersion: userFeatureId.split(':')[0], + manifestDigest: digest, + manifest: {} as any, + featureRef: { + registry: 'ghcr.io', + owner: 'devcontainers', + namespace: 'devcontainers/features', + path: `devcontainers/features/${userFeatureId.split('/').pop()!.split(':')[0]}`, + resource: `ghcr.io/${userFeatureId.split(':')[0]}`, + id: userFeatureId.split('/').pop()!.split(':')[0], + version, + tag: version, + }, + }; + return { + sourceInformation, + computedDigest: digest, + features: [ + { + id: sourceInformation.featureRef.id, + version, + value: true, + included: true, + }, + ], + }; +} + +function makeTarballFeatureSet(userFeatureId: string, tarballUri: string, digest: string): FeatureSet { + const sourceInformation: DirectTarballSourceInformation = { + type: 'direct-tarball', + userFeatureId, + tarballUri, + }; + return { + sourceInformation, + computedDigest: digest, + features: [ + { + id: 'mytarball', + version: '1.0.0', + value: true, + included: true, + }, + ], + }; +} + +const mockConfigFilePath = URI.file('/workspace/myProject/.devcontainer/devcontainer.json'); + +describe('generateLockfile', () => { + + it('includes all features when no additionalFeatures are provided', async () => { + const featureSets: FeatureSet[] = [ + makeOciFeatureSet('ghcr.io/devcontainers/features/node:1', '1.0.0', 'sha256:aaa'), + makeOciFeatureSet('ghcr.io/devcontainers/features/git:1', '1.0.0', 'sha256:bbb'), + ]; + const featuresConfig: FeaturesConfig = { featureSets }; + + const lockfile = await generateLockfile(featuresConfig); + + assert.deepEqual(Object.keys(lockfile.features).sort(), [ + 'ghcr.io/devcontainers/features/git:1', + 'ghcr.io/devcontainers/features/node:1', + ]); + }); + + it('excludes features supplied only via additionalFeatures', async () => { + const featureSets: FeatureSet[] = [ + makeOciFeatureSet('ghcr.io/devcontainers/features/node:1', '1.0.0', 'sha256:aaa'), + makeOciFeatureSet('ghcr.io/devcontainers/features/git:1', '1.0.0', 'sha256:bbb'), + ]; + const featuresConfig: FeaturesConfig = { featureSets }; + + const config: DevContainerConfig = { + configFilePath: mockConfigFilePath, + features: { + 'ghcr.io/devcontainers/features/node:1': {}, + }, + }; + const additionalFeatures = { + 'ghcr.io/devcontainers/features/git:1': true, + }; + + const lockfile = await generateLockfile(featuresConfig, config, additionalFeatures); + + assert.deepEqual(Object.keys(lockfile.features), ['ghcr.io/devcontainers/features/node:1']); + }); + + it('keeps features that appear in both config.features and additionalFeatures', async () => { + const featureSets: FeatureSet[] = [ + makeOciFeatureSet('ghcr.io/devcontainers/features/node:1', '1.0.0', 'sha256:aaa'), + ]; + const featuresConfig: FeaturesConfig = { featureSets }; + + const config: DevContainerConfig = { + configFilePath: mockConfigFilePath, + features: { + 'ghcr.io/devcontainers/features/node:1': {}, + }, + }; + const additionalFeatures = { + 'ghcr.io/devcontainers/features/node:1': true, + }; + + const lockfile = await generateLockfile(featuresConfig, config, additionalFeatures); + + assert.deepEqual(Object.keys(lockfile.features), ['ghcr.io/devcontainers/features/node:1']); + }); + + it('excludes additional-only direct-tarball features', async () => { + const featureSets: FeatureSet[] = [ + makeOciFeatureSet('ghcr.io/devcontainers/features/node:1', '1.0.0', 'sha256:aaa'), + makeTarballFeatureSet('https://example.com/devcontainer-feature-mytarball.tgz', 'https://example.com/devcontainer-feature-mytarball.tgz', 'sha256:ccc'), + ]; + const featuresConfig: FeaturesConfig = { featureSets }; + + const config: DevContainerConfig = { + configFilePath: mockConfigFilePath, + features: { + 'ghcr.io/devcontainers/features/node:1': {}, + }, + }; + const additionalFeatures = { + 'https://example.com/devcontainer-feature-mytarball.tgz': true, + }; + + const lockfile = await generateLockfile(featuresConfig, config, additionalFeatures); + + assert.deepEqual(Object.keys(lockfile.features), ['ghcr.io/devcontainers/features/node:1']); + }); + + it('excludes all features when config.features is empty and additionalFeatures provides them all', async () => { + const featureSets: FeatureSet[] = [ + makeOciFeatureSet('ghcr.io/devcontainers/features/node:1', '1.0.0', 'sha256:aaa'), + makeOciFeatureSet('ghcr.io/devcontainers/features/git:1', '1.0.0', 'sha256:bbb'), + ]; + const featuresConfig: FeaturesConfig = { featureSets }; + + const config: DevContainerConfig = { + configFilePath: mockConfigFilePath, + }; + const additionalFeatures = { + 'ghcr.io/devcontainers/features/node:1': true, + 'ghcr.io/devcontainers/features/git:1': true, + }; + + const lockfile = await generateLockfile(featuresConfig, config, additionalFeatures); + + assert.deepEqual(lockfile.features, {}); + }); +}); diff --git a/src/test/container-features/lockfile.test.ts b/src/test/container-features/lockfile.test.ts index 57034e6f2..2e7f035ef 100644 --- a/src/test/container-features/lockfile.test.ts +++ b/src/test/container-features/lockfile.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as path from 'path'; import * as semver from 'semver'; import { shellExec } from '../testUtils'; -import { cpLocal, readLocalFile, rmLocal } from '../../spec-utils/pfs'; +import { cpLocal, readLocalFile, rmLocal, writeLocalFile } from '../../spec-utils/pfs'; const pkg = require('../../../package.json'); @@ -258,4 +258,298 @@ describe('Lockfile', function () { await cleanup(); } }); + + it('outdated command should work with default workspace folder', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile-outdated-command'); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + + const originalCwd = process.cwd(); + try { + process.chdir(workspaceFolder); + const res = await shellExec(`${absoluteCli} outdated --output-format json`); + const response = JSON.parse(res.stdout); + + // Should have same structure as the test with explicit workspace-folder + assert.ok(response.features); + assert.ok(response.features['ghcr.io/devcontainers/features/git:1.0']); + assert.strictEqual(response.features['ghcr.io/devcontainers/features/git:1.0'].current, '1.0.4'); + } finally { + process.chdir(originalCwd); + } + }); + + it('lockfile ends with trailing newline', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile'); + + const lockfilePath = path.join(workspaceFolder, '.devcontainer-lock.json'); + await rmLocal(lockfilePath, { force: true }); + + const res = await shellExec(`${cli} build --workspace-folder ${workspaceFolder} --experimental-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + const actual = (await readLocalFile(lockfilePath)).toString(); + assert.ok(actual.endsWith('\n'), 'Lockfile should end with a trailing newline'); + }); + + it('frozen lockfile matches despite formatting differences', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile-frozen'); + const lockfilePath = path.join(workspaceFolder, '.devcontainer-lock.json'); + + // Read the existing lockfile, strip trailing newline to create a byte-different but semantically identical file + const original = (await readLocalFile(lockfilePath)).toString(); + const stripped = original.replace(/\n$/, ''); + assert.notEqual(original, stripped, 'Test setup: should have removed trailing newline'); + assert.deepEqual(JSON.parse(original), JSON.parse(stripped), 'Test setup: JSON content should be identical'); + + try { + await writeLocalFile(lockfilePath, Buffer.from(stripped)); + + // Frozen lockfile should succeed because JSON content is the same + const res = await shellExec(`${cli} build --workspace-folder ${workspaceFolder} --experimental-lockfile --experimental-frozen-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success', 'Frozen lockfile should not fail when only formatting differs'); + const actual = (await readLocalFile(lockfilePath)).toString(); + assert.strictEqual(actual, stripped, 'Frozen lockfile should remain unchanged when only formatting differs'); + } finally { + // Restore original lockfile + await writeLocalFile(lockfilePath, Buffer.from(original)); + } + }); + + it('upgrade command should work with default workspace folder', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile-upgrade-command'); + const absoluteTmpPath = path.resolve(__dirname, 'tmp'); + const absoluteCli = `npx --prefix ${absoluteTmpPath} devcontainer`; + + const lockfilePath = path.join(workspaceFolder, '.devcontainer-lock.json'); + await cpLocal(path.join(workspaceFolder, 'outdated.devcontainer-lock.json'), lockfilePath); + + const originalCwd = process.cwd(); + try { + process.chdir(workspaceFolder); + await shellExec(`${absoluteCli} upgrade`); + const actual = await readLocalFile(lockfilePath); + const expected = await readLocalFile(path.join(workspaceFolder, 'upgraded.devcontainer-lock.json')); + assert.equal(actual.toString(), expected.toString()); + } finally { + process.chdir(originalCwd); + } + }); + + it('frozen lockfile fails when lockfile does not exist', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile-frozen-no-lockfile'); + const lockfilePath = path.join(workspaceFolder, '.devcontainer-lock.json'); + await rmLocal(lockfilePath, { force: true }); + + try { + throw await shellExec(`${cli} build --workspace-folder ${workspaceFolder} --experimental-lockfile --experimental-frozen-lockfile`); + } catch (res) { + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'error'); + assert.equal(response.message, 'Lockfile does not exist.'); + } + }); + + it('corrupt lockfile causes build error', async () => { + const workspaceFolder = path.join(__dirname, 'configs/lockfile'); + const lockfilePath = path.join(workspaceFolder, '.devcontainer-lock.json'); + const expectedPath = path.join(workspaceFolder, 'expected.devcontainer-lock.json'); + + try { + // Write invalid JSON to the lockfile + await writeLocalFile(lockfilePath, Buffer.from('this is not valid json{{{')); + + try { + throw await shellExec(`${cli} build --workspace-folder ${workspaceFolder} --experimental-lockfile`); + } catch (res) { + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'error'); + } + } finally { + // Restore from the known-good expected lockfile + await cpLocal(expectedPath, lockfilePath); + } + }); + + // -- Graduated lockfile tests -- + + async function isolateFixture(name: string): Promise { + const src = path.join(__dirname, 'configs', name); + const dst = path.join(__dirname, 'tmp-fixtures', `${name}-${process.hrtime.bigint()}`); + await shellExec(`mkdir -p ${path.dirname(dst)} && cp -r ${src} ${dst}`); + return dst; + } + + async function lockfileExists(p: string): Promise { + return readLocalFile(p).then(() => true, err => { + if (err?.code === 'ENOENT') { + return false; + } + throw err; + }); + } + + after(async () => { + await shellExec(`rm -rf ${path.join(__dirname, 'tmp-fixtures')}`); + }); + + it('auto-generates lockfile by default without any flags', async () => { + const tmpDir = await isolateFixture('lockfile'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + // Remove the committed lockfile so we can verify auto-creation from scratch. + await rmLocal(lockfilePath, { force: true }); + + const res = await shellExec(`${cli} build --workspace-folder ${tmpDir}`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + const actual = await readLocalFile(lockfilePath); + const expected = await readLocalFile(path.join(tmpDir, 'expected.devcontainer-lock.json')); + assert.equal(actual.toString(), expected.toString()); + }); + + it('--no-lockfile prevents lockfile creation', async () => { + const tmpDir = await isolateFixture('lockfile-no-lockfile'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + + const res = await shellExec(`${cli} build --workspace-folder ${tmpDir} --no-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + assert.equal(await lockfileExists(lockfilePath), false, 'Lockfile should not be created when --no-lockfile is set'); + }); + + it('--no-lockfile ignores existing lockfile', async () => { + const tmpDir = await isolateFixture('lockfile-frozen'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + + const lockfileBefore = (await readLocalFile(lockfilePath)).toString(); + + const res = await shellExec(`${cli} build --workspace-folder ${tmpDir} --no-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + const lockfileAfter = (await readLocalFile(lockfilePath)).toString(); + assert.equal(lockfileAfter, lockfileBefore, 'Lockfile should be unchanged when --no-lockfile is set'); + }); + + it('--frozen-lockfile succeeds with matching lockfile', async () => { + const tmpDir = await isolateFixture('lockfile-frozen'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + const lockfileBefore = (await readLocalFile(lockfilePath)).toString(); + + const res = await shellExec(`${cli} build --workspace-folder ${tmpDir} --frozen-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + const lockfileAfter = (await readLocalFile(lockfilePath)).toString(); + assert.equal(lockfileAfter, lockfileBefore, 'Lockfile should be unchanged'); + }); + + it('--frozen-lockfile fails when lockfile missing', async () => { + const tmpDir = await isolateFixture('lockfile-no-lockfile'); + + try { + throw await shellExec(`${cli} build --workspace-folder ${tmpDir} --frozen-lockfile`); + } catch (res) { + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'error'); + assert.equal(response.message, 'Lockfile does not exist.'); + } + }); + + for (const secondFlag of ['--frozen-lockfile', '--experimental-frozen-lockfile', '--experimental-lockfile']) { + it(`--no-lockfile and ${secondFlag} are mutually exclusive`, async () => { + const tmpDir = await isolateFixture('lockfile-no-lockfile'); + + try { + throw await shellExec(`${cli} build --workspace-folder ${tmpDir} --no-lockfile ${secondFlag}`); + } catch (res) { + assert.match(res.stderr, /mutually exclusive/i, 'Should fail with mutually exclusive error'); + } + }); + } + + for (const { fixture, flag } of [ + { fixture: 'lockfile', flag: '--experimental-lockfile' }, + { fixture: 'lockfile-frozen', flag: '--experimental-frozen-lockfile' }, + ]) { + it(`deprecation warning for ${flag}`, async () => { + const tmpDir = await isolateFixture(fixture); + + const res = await shellExec(`${cli} build --workspace-folder ${tmpDir} ${flag}`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + assert.ok(res.stderr.includes(`${flag} is deprecated`), 'Should emit deprecation warning'); + }); + } + + it('devcontainer up auto-generates lockfile by default', async () => { + const tmpDir = await isolateFixture('lockfile-no-lockfile'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + const idLabel = `test-lockfile-up=${process.hrtime.bigint()}`; + + try { + const res = await shellExec(`${cli} up --workspace-folder ${tmpDir} --id-label ${idLabel}`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + const actual = await readLocalFile(lockfilePath); + assert.ok(actual.toString().trim().length > 0, 'Lockfile should have been created'); + const parsed = JSON.parse(actual.toString()); + assert.ok(parsed.features, 'Lockfile should contain features'); + } finally { + // Clean up by id-label so cleanup happens even if `up` failed before returning a containerId. + await shellExec(`docker rm -f $(docker ps -aq --filter label=${idLabel}) 2>/dev/null || true`, undefined, true, true); + } + }); + + it('devcontainer up --frozen-lockfile succeeds with matching lockfile', async () => { + const tmpDir = await isolateFixture('lockfile-frozen'); + const lockfilePath = path.join(tmpDir, '.devcontainer-lock.json'); + const lockfileBefore = (await readLocalFile(lockfilePath)).toString(); + const idLabel = `test-lockfile-up-frozen=${process.hrtime.bigint()}`; + + try { + const res = await shellExec(`${cli} up --workspace-folder ${tmpDir} --id-label ${idLabel} --frozen-lockfile`); + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'success'); + + const lockfileAfter = (await readLocalFile(lockfilePath)).toString(); + assert.equal(lockfileAfter, lockfileBefore, 'Lockfile should be unchanged'); + } finally { + await shellExec(`docker rm -f $(docker ps -aq --filter label=${idLabel}) 2>/dev/null || true`, undefined, true, true); + } + }); + + it('devcontainer up --frozen-lockfile fails when lockfile missing', async () => { + const tmpDir = await isolateFixture('lockfile-no-lockfile'); + const idLabel = `test-lockfile-up-frozen-fail=${process.hrtime.bigint()}`; + + try { + throw await shellExec(`${cli} up --workspace-folder ${tmpDir} --id-label ${idLabel} --frozen-lockfile`); + } catch (res) { + const response = JSON.parse(res.stdout); + assert.equal(response.outcome, 'error'); + assert.equal(response.message, 'Lockfile does not exist.'); + } finally { + await shellExec(`docker rm -f $(docker ps -aq --filter label=${idLabel}) 2>/dev/null || true`, undefined, true, true); + } + }); + + it('read-only commands do not create a lockfile', async () => { + const readConfigTmpDir = await isolateFixture('lockfile-no-lockfile'); + const readConfigLockfilePath = path.join(readConfigTmpDir, '.devcontainer-lock.json'); + + // read-configuration should not create a lockfile + await shellExec(`${cli} read-configuration --workspace-folder ${readConfigTmpDir} --include-features-configuration`, undefined, true); + assert.equal(await lockfileExists(readConfigLockfilePath), false, 'read-configuration should not create a lockfile'); + + const resolveDepsTmpDir = await isolateFixture('lockfile-no-lockfile'); + const resolveDepsLockfilePath = path.join(resolveDepsTmpDir, '.devcontainer-lock.json'); + + await shellExec(`${cli} features resolve-dependencies --workspace-folder ${resolveDepsTmpDir}`, undefined, true); + assert.equal(await lockfileExists(resolveDepsLockfilePath), false, 'features resolve-dependencies should not create a lockfile'); + }); }); \ No newline at end of file diff --git a/src/test/container-templates/templatesCLICommands.test.ts b/src/test/container-templates/templatesCLICommands.test.ts index babb4f8f2..1a91e2a2d 100644 --- a/src/test/container-templates/templatesCLICommands.test.ts +++ b/src/test/container-templates/templatesCLICommands.test.ts @@ -62,6 +62,60 @@ describe('tests apply command', async function () { // Assert that the Feature included in the command was added. assert.match(file, /"ghcr.io\/devcontainers\/features\/azure-cli:1": {\n/); }); + + it('templates apply subcommand with default workspace folder', async function () { + const testOutputPath = path.resolve(__dirname, 'tmp-default-workspace'); + const originalCwd = process.cwd(); + + try { + // Create and change to test output directory to test default workspace folder behavior + await shellExec(`rm -rf ${testOutputPath}`); + await shellExec(`mkdir -p ${testOutputPath}`); + process.chdir(testOutputPath); + + // Use absolute path to CLI to prevent npm ENOENT errors + const absoluteTmpPath = path.resolve(originalCwd, tmp); + const absoluteCliPath = `npx --prefix ${absoluteTmpPath} devcontainer`; + + let success = false; + let result: ExecResult | undefined = undefined; + + try { + // Run without --workspace-folder to test default behavior + result = await shellExec(`${absoluteCliPath} templates apply \ + --template-id ghcr.io/devcontainers/templates/docker-from-docker:latest \ + --template-args '{ "installZsh": "false", "upgradePackages": "true", "dockerVersion": "20.10", "moby": "true", "enableNonRootDocker": "true" }' \ + --log-level trace`); + success = true; + + } catch (error) { + assert.fail('templates apply sub-command should not throw when using default workspace folder'); + } + + assert.isTrue(success); + assert.isDefined(result); + assert.strictEqual(result.stdout.trim(), '{"files":["./.devcontainer/devcontainer.json"]}'); + + // Verify the file was created in the current working directory (default workspace folder) + const file = (await readLocalFile(path.join(testOutputPath, '.devcontainer', 'devcontainer.json'))).toString(); + + assert.match(file, /"name": "Docker from Docker"/); + assert.match(file, /"installZsh": "false"/); + assert.match(file, /"upgradePackages": "true"/); + assert.match(file, /"version": "20.10"/); + assert.match(file, /"moby": "true"/); + assert.match(file, /"enableNonRootDocker": "true"/); + + // Assert that the Features included in the template were not removed. + assert.match(file, /"ghcr.io\/devcontainers\/features\/common-utils:1": {\n/); + assert.match(file, /"ghcr.io\/devcontainers\/features\/docker-from-docker:1": {\n/); + + } finally { + process.chdir(originalCwd); + // Clean up test directory + await shellExec(`rm -rf ${testOutputPath}`); + } + }); }); describe('tests packageTemplates()', async function () { diff --git a/src/test/dockerfileUtils.test.ts b/src/test/dockerfileUtils.test.ts index 979375b91..1811f1ce2 100644 --- a/src/test/dockerfileUtils.test.ts +++ b/src/test/dockerfileUtils.test.ts @@ -178,7 +178,7 @@ FROM ubuntu:latest as dev const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => { assert.strictEqual(imageName, 'ubuntu:latest'); return details; - }, dockerfile, {}, undefined, testSubstitute, nullLog, false); + }, dockerfile, {}, undefined, testSubstitute, nullLog, false, { os: 'linux', arch: 'arm64' }, { os: 'linux', arch: 'amd64' }); assert.strictEqual(info.user, 'imageUser'); assert.strictEqual(info.metadata.config.length, 1); assert.strictEqual(info.metadata.config[0].id, 'testid-substituted'); @@ -206,11 +206,42 @@ USER dockerfileUserB const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => { assert.strictEqual(imageName, 'ubuntu:latest'); return details; - }, dockerfile, {}, undefined, testSubstitute, nullLog, false); + }, dockerfile, {}, undefined, testSubstitute, nullLog, false, { os: 'linux', arch: 'arm64' }, { os: 'linux', arch: 'amd64' }); assert.strictEqual(info.user, 'dockerfileUserB'); assert.strictEqual(info.metadata.config.length, 0); assert.strictEqual(info.metadata.raw.length, 0); }); + + it('for a USER in a multiarch image', async () => { + const dockerfile = ` +FROM ubuntu:latest as base-amd64 +USER amd64_user + +FROM ubuntu:latest as base-arm64 +USER arm64_user + +FROM base-\${TARGETARCH} +`; + const details: ImageDetails = { + Id: '123', + Config: { + User: 'imageUser', + Env: null, + Labels: null, + Entrypoint: null, + Cmd: null + }, + Os: 'linux', + Architecture: 'amd64' + }; + const info = await internalGetImageBuildInfoFromDockerfile(async (imageName) => { + assert.strictEqual(imageName, 'ubuntu:latest'); + return details; + }, dockerfile, {}, undefined, testSubstitute, nullLog, false, { os: 'linux', arch: 'arm64' }, { os: 'linux', arch: 'amd64' }); + assert.strictEqual(info.user, 'amd64_user'); + assert.strictEqual(info.metadata.config.length, 0); + assert.strictEqual(info.metadata.raw.length, 0); + }); }); describe('findBaseImage', () => { @@ -391,7 +422,7 @@ describe('findUserStatement', () => { USER user1 `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user1'); }); @@ -401,7 +432,7 @@ ARG IMAGE_USER=user2 USER $IMAGE_USER `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user2'); }); @@ -413,7 +444,7 @@ USER $IMAGE_USER const extracted = extractDockerfile(dockerfile); const user = findUserStatement(extracted, { IMAGE_USER: 'user3' - }, {}, undefined); + }, {}, {}, undefined); assert.strictEqual(user, 'user3'); }); @@ -429,7 +460,7 @@ FROM image4 as stage4 USER user4 `; const extracted = extractDockerfile(dockerfile); - const image = findUserStatement(extracted, {}, {}, 'stage2'); + const image = findUserStatement(extracted, {}, {}, {}, 'stage2'); assert.strictEqual(image, 'user3_2'); }); @@ -441,7 +472,7 @@ ARG USERNAME=user2 USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user2'); }); @@ -455,7 +486,7 @@ FROM one as two USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user1'); }); @@ -466,7 +497,7 @@ FROM debian USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user1'); }); @@ -478,7 +509,7 @@ ARG USERNAME USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user1'); }); @@ -488,7 +519,7 @@ FROM debian USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, undefined); }); @@ -500,7 +531,7 @@ ENV USERNAME=user2 USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user2'); }); @@ -512,7 +543,7 @@ ENV USERNAME2=\${USERNAME1} USER \${USERNAME2} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'user1'); }); @@ -524,7 +555,7 @@ ENV USERNAME2=user2 USER A\${USERNAME1}A\${USERNAME2}A `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, {}, undefined); + const user = findUserStatement(extracted, {}, {}, {}, undefined); assert.strictEqual(user, 'Auser1Auser2A'); }); @@ -534,7 +565,7 @@ FROM mybase USER \${USERNAME} `; const extracted = extractDockerfile(dockerfile); - const user = findUserStatement(extracted, {}, { USERNAME: 'user1' }, undefined); + const user = findUserStatement(extracted, {}, { USERNAME: 'user1' }, {}, undefined); assert.strictEqual(user, 'user1'); }); }); diff --git a/src/test/imageMetadata.test.ts b/src/test/imageMetadata.test.ts index 24045ac4e..fb78df0f5 100644 --- a/src/test/imageMetadata.test.ts +++ b/src/test/imageMetadata.test.ts @@ -432,6 +432,22 @@ describe('Image Metadata', function () { assert.strictEqual(label.replace(/ \\\n/g, ''), `LABEL devcontainer.metadata="${JSON.stringify(expected).replace(/"/g, '\\"')}"`); }); + it('should create array label for single metadata entry (docker-compose with Dockerfile, no features)', () => { + // When there is only one metadata entry, the label should still be a JSON array. + // Regression test for https://github.com/devcontainers/cli/issues/1054 + const label = getDevcontainerMetadataLabel(configWithRaw([ + { + remoteUser: 'testUser', + } + ])); + const expected = [ + { + remoteUser: 'testUser', + } + ]; + assert.strictEqual(label.replace(/ \\\n/g, ''), `LABEL devcontainer.metadata="${JSON.stringify(expected).replace(/"/g, '\\"')}"`); + }); + it('should merge metadata from devcontainer.json and features', () => { const merged = mergeConfiguration({ configFilePath: URI.parse('file:///devcontainer.json'), diff --git a/src/test/labelPathNormalization.test.ts b/src/test/labelPathNormalization.test.ts new file mode 100644 index 000000000..2997b5632 --- /dev/null +++ b/src/test/labelPathNormalization.test.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from 'chai'; +import { normalizeDevContainerLabelPath } from '../spec-node/utils'; + +describe('normalizeDevContainerLabelPath', function () { + it('lowercases Windows drive letters', function () { + assert.equal( + normalizeDevContainerLabelPath('win32', 'C:\\CodeBlocks\\remill'), + 'c:\\CodeBlocks\\remill' + ); + }); + + it('normalizes Windows path separators', function () { + assert.equal( + normalizeDevContainerLabelPath('win32', 'C:/CodeBlocks/remill/.devcontainer/devcontainer.json'), + 'c:\\CodeBlocks\\remill\\.devcontainer\\devcontainer.json' + ); + }); + + it('leaves non-Windows paths unchanged', function () { + assert.equal( + normalizeDevContainerLabelPath('linux', '/workspaces/remill'), + '/workspaces/remill' + ); + }); +}); diff --git a/src/test/testUtils.ts b/src/test/testUtils.ts index cd48c5359..22597dce2 100644 --- a/src/test/testUtils.ts +++ b/src/test/testUtils.ts @@ -154,16 +154,18 @@ export async function createCLIParams(hostPath: string) { env: cliHost.env, output, }, 'docker', 'docker-compose'); + const buildPlatformInfo = { + os: mapNodeOSToGOOS(cliHost.platform), + arch: mapNodeArchitectureToGOARCH(cliHost.arch), + }; const cliParams: DockerCLIParameters = { cliHost, dockerCLI: 'docker', dockerComposeCLI, env: {}, output, - platformInfo: { - os: mapNodeOSToGOOS(cliHost.platform), - arch: mapNodeArchitectureToGOARCH(cliHost.arch), - } + buildPlatformInfo, + targetPlatformInfo: buildPlatformInfo, }; return cliParams; } diff --git a/src/test/tsconfig.json b/src/test/tsconfig.json index 0b742af1e..b5be01ce1 100644 --- a/src/test/tsconfig.json +++ b/src/test/tsconfig.json @@ -1,7 +1,11 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "resolveJsonModule": true + "resolveJsonModule": true, + "types": [ + "node", + "mocha" + ] }, "references": [ { diff --git a/src/test/utils.test.ts b/src/test/utils.test.ts new file mode 100644 index 000000000..b266b9d2a --- /dev/null +++ b/src/test/utils.test.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; + +import { isBuildxCacheToInline } from '../spec-node/utils'; + +describe('Utils', function () { + describe('isBuildxCacheToInline', function () { + it('returns false for undefined or empty', () => { + assert.strictEqual(isBuildxCacheToInline(undefined), false); + assert.strictEqual(isBuildxCacheToInline(''), false); + }); + + it('returns true for inline cache type', () => { + assert.strictEqual(isBuildxCacheToInline('type=inline'), true); + assert.strictEqual(isBuildxCacheToInline('type = inline'), true); + assert.strictEqual(isBuildxCacheToInline('type=INLINE'), true); + assert.strictEqual(isBuildxCacheToInline('mode=max,type=inline,compression=zstd'), true); + }); + + it('returns false for non-inline cache type', () => { + assert.strictEqual(isBuildxCacheToInline('type=registry'), false); + assert.strictEqual(isBuildxCacheToInline('type=local'), false); + assert.strictEqual(isBuildxCacheToInline('inline'), false); + }); + }); +}); diff --git a/src/test/workspaceConfiguration.test.ts b/src/test/workspaceConfiguration.test.ts new file mode 100644 index 000000000..1e0b7e992 --- /dev/null +++ b/src/test/workspaceConfiguration.test.ts @@ -0,0 +1,506 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import { assert } from 'chai'; +import * as path from 'path'; +import { getWorkspaceConfiguration } from '../spec-node/utils'; +import { CLIHost } from '../spec-common/cliHost'; +import { Workspace } from '../spec-utils/workspaces'; +import { nullLog } from '../spec-utils/log'; + +function createMockCLIHost(options: { + platform: NodeJS.Platform; + files?: Record; + useFileHost?: boolean; // Use FileHost path in findGitRootFolder (for testing parent folder git root) +}): CLIHost { + const { platform, files = {}, useFileHost = false } = options; + const pathModule = platform === 'win32' ? path.win32 : path.posix; + const baseHost = { + type: 'local' as const, + platform, + arch: 'x64' as const, + path: pathModule, + cwd: platform === 'win32' ? 'C:\\' : '/', + env: {}, + ptyExec: () => { throw new Error('Not implemented'); }, + homedir: async () => platform === 'win32' ? 'C:\\Users\\test' : '/home/test', + tmpdir: async () => platform === 'win32' ? 'C:\\tmp' : '/tmp', + isFile: async (filepath: string) => filepath in files, + isFolder: async () => false, + readFile: async (filepath: string) => { + if (filepath in files) { + return Buffer.from(files[filepath]); + } + throw new Error(`File not found: ${filepath}`); + }, + writeFile: async () => { }, + rename: async () => { }, + mkdirp: async () => { }, + readDir: async () => [], + getUsername: async () => 'test', + toCommonURI: async () => undefined, + connect: () => { throw new Error('Not implemented'); }, + }; + // If useFileHost is true, don't include exec so findGitRootFolder uses the FileHost code path + if (useFileHost) { + return baseHost as unknown as CLIHost; + } + return { + ...baseHost, + exec: () => { throw new Error('Not implemented'); }, + } as CLIHost; +} + +function createWorkspace(rootFolderPath: string, configFolderPath?: string): Workspace { + return { + isWorkspaceFile: false, + workspaceOrFolderPath: rootFolderPath, + rootFolderPath, + configFolderPath: configFolderPath || rootFolderPath, + }; +} + +type TestPlatform = 'linux' | 'darwin' | 'win32'; +const platforms: TestPlatform[] = ['linux', 'darwin', 'win32']; + +describe('getWorkspaceConfiguration', function () { + + for (const platform of platforms) { + describe(`platform: ${platform}`, function () { + + describe('basic workspace mounting', function () { + + it('should mount workspace at /workspaces/', async () => { + const p = { + linux: { projectPath: '/home/user/project', consistency: '' }, + darwin: { projectPath: '/Users/user/project', consistency: ',consistency=consistent' }, + win32: { projectPath: 'C:\\Users\\user\\project', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ platform }); + const workspace = createWorkspace(p.projectPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + false, + false, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/project'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.projectPath},target=/workspaces/project${p.consistency}`); + assert.isUndefined(result.additionalMountString); + }); + + }); + + describe('git worktree handling', function () { + + it('should not add additional mount when .git is not a file', async () => { + const p = { + linux: { projectPath: '/home/user/project' }, + darwin: { projectPath: '/Users/user/project' }, + win32: { projectPath: 'C:\\Users\\user\\project' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: {} + }); + const workspace = createWorkspace(p.projectPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.isUndefined(result.additionalMountString); + }); + + it('should not add additional mount when gitdir is an absolute path', async () => { + const p = { + linux: { projectPath: '/home/user/project', gitFile: '/home/user/project/.git', absoluteGitdir: 'gitdir: /absolute/path/to/.git/worktrees/project' }, + darwin: { projectPath: '/Users/user/project', gitFile: '/Users/user/project/.git', absoluteGitdir: 'gitdir: /absolute/path/to/.git/worktrees/project' }, + win32: { projectPath: 'C:\\Users\\user\\project', gitFile: 'C:\\Users\\user\\project\\.git', absoluteGitdir: 'gitdir: C:/absolute/path/to/.git/worktrees/project' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitFile]: p.absoluteGitdir + } + }); + const workspace = createWorkspace(p.projectPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.isUndefined(result.additionalMountString); + }); + + it('should not add additional mount when mountGitWorktreeCommonDir is false', async () => { + const p = { + linux: { worktreePath: '/home/user/worktrees/feature', gitFile: '/home/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', consistency: '' }, + darwin: { worktreePath: '/Users/user/worktrees/feature', gitFile: '/Users/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\worktrees\\feature', gitFile: 'C:\\Users\\user\\worktrees\\feature\\.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitFile]: p.gitdir + } + }); + const workspace = createWorkspace(p.worktreePath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + false, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/feature'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.worktreePath},target=/workspaces/feature${p.consistency}`); + assert.isUndefined(result.additionalMountString); + }); + + it('should not add additional mount when mountGitWorktreeCommonDir is false with workspace in subfolder', async () => { + const p = { + linux: { worktreePath: '/home/user/worktrees/feature', gitConfigFile: '/home/user/worktrees/feature/.git/config', gitFile: '/home/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', subfolderPath: '/home/user/worktrees/feature/packages/app', consistency: '' }, + darwin: { worktreePath: '/Users/user/worktrees/feature', gitConfigFile: '/Users/user/worktrees/feature/.git/config', gitFile: '/Users/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', subfolderPath: '/Users/user/worktrees/feature/packages/app', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\worktrees\\feature', gitConfigFile: 'C:\\Users\\user\\worktrees\\feature\\.git\\config', gitFile: 'C:\\Users\\user\\worktrees\\feature\\.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', subfolderPath: 'C:\\Users\\user\\worktrees\\feature\\packages\\app', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]', + [p.gitFile]: p.gitdir + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + false, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/feature/packages/app'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.worktreePath},target=/workspaces/feature${p.consistency}`); + assert.isUndefined(result.additionalMountString); + }); + + it('should add additional mount when gitdir is a relative path', async () => { + const p = { + linux: { worktreePath: '/home/user/worktrees/feature', gitFile: '/home/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', repoGitPath: '/home/user/repo/.git', consistency: '' }, + darwin: { worktreePath: '/Users/user/worktrees/feature', gitFile: '/Users/user/worktrees/feature/.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', repoGitPath: '/Users/user/repo/.git', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\worktrees\\feature', gitFile: 'C:\\Users\\user\\worktrees\\feature\\.git', gitdir: 'gitdir: ../../repo/.git/worktrees/feature', repoGitPath: 'C:\\Users\\user\\repo\\.git', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitFile]: p.gitdir + } + }); + const workspace = createWorkspace(p.worktreePath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/worktrees/feature'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.worktreePath},target=/workspaces/worktrees/feature${p.consistency}`); + assert.strictEqual(result.additionalMountString, `type=bind,source=${p.repoGitPath},target=/workspaces/repo/.git${p.consistency}`); + }); + + it('should handle gitdir with single level up', async () => { + const p = { + linux: { worktreePath: '/home/user/repo-worktree', gitFile: '/home/user/repo-worktree/.git', gitdir: 'gitdir: ../repo/.git/worktrees/worktree', repoGitPath: '/home/user/repo/.git', consistency: '' }, + darwin: { worktreePath: '/Users/user/repo-worktree', gitFile: '/Users/user/repo-worktree/.git', gitdir: 'gitdir: ../repo/.git/worktrees/worktree', repoGitPath: '/Users/user/repo/.git', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\repo-worktree', gitFile: 'C:\\Users\\user\\repo-worktree\\.git', gitdir: 'gitdir: ../repo/.git/worktrees/worktree', repoGitPath: 'C:\\Users\\user\\repo\\.git', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitFile]: p.gitdir + } + }); + const workspace = createWorkspace(p.worktreePath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/repo-worktree'); + assert.strictEqual(result.additionalMountString, `type=bind,source=${p.repoGitPath},target=/workspaces/repo/.git${p.consistency}`); + }); + + it('should handle worktree two levels deep from common parent with main repo', async () => { + const p = { + linux: { worktreePath: '/home/user/projects/worktrees/feature', gitFile: '/home/user/projects/worktrees/feature/.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', repoGitPath: '/home/user/projects/repos/main/.git', consistency: '' }, + darwin: { worktreePath: '/Users/user/projects/worktrees/feature', gitFile: '/Users/user/projects/worktrees/feature/.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', repoGitPath: '/Users/user/projects/repos/main/.git', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\projects\\worktrees\\feature', gitFile: 'C:\\Users\\user\\projects\\worktrees\\feature\\.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', repoGitPath: 'C:\\Users\\user\\projects\\repos\\main\\.git', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitFile]: p.gitdir + } + }); + const workspace = createWorkspace(p.worktreePath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/worktrees/feature'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.worktreePath},target=/workspaces/worktrees/feature${p.consistency}`); + assert.strictEqual(result.additionalMountString, `type=bind,source=${p.repoGitPath},target=/workspaces/repos/main/.git${p.consistency}`); + }); + + it('should handle worktree two levels deep with workspace in subfolder', async () => { + const p = { + linux: { worktreePath: '/home/user/projects/worktrees/feature', gitConfigFile: '/home/user/projects/worktrees/feature/.git/config', gitFile: '/home/user/projects/worktrees/feature/.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', subfolderPath: '/home/user/projects/worktrees/feature/packages/app', repoGitPath: '/home/user/projects/repos/main/.git', consistency: '' }, + darwin: { worktreePath: '/Users/user/projects/worktrees/feature', gitConfigFile: '/Users/user/projects/worktrees/feature/.git/config', gitFile: '/Users/user/projects/worktrees/feature/.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', subfolderPath: '/Users/user/projects/worktrees/feature/packages/app', repoGitPath: '/Users/user/projects/repos/main/.git', consistency: ',consistency=consistent' }, + win32: { worktreePath: 'C:\\Users\\user\\projects\\worktrees\\feature', gitConfigFile: 'C:\\Users\\user\\projects\\worktrees\\feature\\.git\\config', gitFile: 'C:\\Users\\user\\projects\\worktrees\\feature\\.git', gitdir: 'gitdir: ../../repos/main/.git/worktrees/feature', subfolderPath: 'C:\\Users\\user\\projects\\worktrees\\feature\\packages\\app', repoGitPath: 'C:\\Users\\user\\projects\\repos\\main\\.git', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]', + [p.gitFile]: p.gitdir + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/worktrees/feature/packages/app'); + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.worktreePath},target=/workspaces/worktrees/feature${p.consistency}`); + assert.strictEqual(result.additionalMountString, `type=bind,source=${p.repoGitPath},target=/workspaces/repos/main/.git${p.consistency}`); + }); + + }); + + describe('git root in parent folder', function () { + + it('should mount from git root when .git/config is in parent folder', async () => { + const p = { + linux: { repoPath: '/home/user/repo', gitConfigFile: '/home/user/repo/.git/config', subfolderPath: '/home/user/repo/packages/frontend', consistency: '' }, + darwin: { repoPath: '/Users/user/repo', gitConfigFile: '/Users/user/repo/.git/config', subfolderPath: '/Users/user/repo/packages/frontend', consistency: ',consistency=consistent' }, + win32: { repoPath: 'C:\\Users\\user\\repo', gitConfigFile: 'C:\\Users\\user\\repo\\.git\\config', subfolderPath: 'C:\\Users\\user\\repo\\packages\\frontend', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]' + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.repoPath},target=/workspaces/repo${p.consistency}`); + assert.strictEqual(result.workspaceFolder, '/workspaces/repo/packages/frontend'); + assert.isUndefined(result.additionalMountString); + }); + + it('should mount workspace folder when mountWorkspaceGitRoot is false even with .git in parent', async () => { + const p = { + linux: { gitConfigFile: '/home/user/repo/.git/config', subfolderPath: '/home/user/repo/packages/frontend', consistency: '' }, + darwin: { gitConfigFile: '/Users/user/repo/.git/config', subfolderPath: '/Users/user/repo/packages/frontend', consistency: ',consistency=consistent' }, + win32: { gitConfigFile: 'C:\\Users\\user\\repo\\.git\\config', subfolderPath: 'C:\\Users\\user\\repo\\packages\\frontend', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]' + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + false, + false, + nullLog + ); + + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.subfolderPath},target=/workspaces/frontend${p.consistency}`); + assert.strictEqual(result.workspaceFolder, '/workspaces/frontend'); + }); + + it('should handle deeply nested workspace in git repo', async () => { + const p = { + linux: { monorepoPath: '/home/user/monorepo', gitConfigFile: '/home/user/monorepo/.git/config', subfolderPath: '/home/user/monorepo/packages/apps/web', consistency: '' }, + darwin: { monorepoPath: '/Users/user/monorepo', gitConfigFile: '/Users/user/monorepo/.git/config', subfolderPath: '/Users/user/monorepo/packages/apps/web', consistency: ',consistency=consistent' }, + win32: { monorepoPath: 'C:\\Users\\user\\monorepo', gitConfigFile: 'C:\\Users\\user\\monorepo\\.git\\config', subfolderPath: 'C:\\Users\\user\\monorepo\\packages\\apps\\web', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]' + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceMount, `type=bind,source=${p.monorepoPath},target=/workspaces/monorepo${p.consistency}`); + assert.strictEqual(result.workspaceFolder, '/workspaces/monorepo/packages/apps/web'); + }); + + it('should handle worktree with git root in parent folder', async () => { + const p = { + linux: { repoPath: '/home/user/repo', gitConfigFile: '/home/user/repo/.git/config', gitFile: '/home/user/repo/.git', gitdir: 'gitdir: ../main-repo/.git/worktrees/repo', mainRepoGitPath: '/home/user/main-repo/.git', subfolderPath: '/home/user/repo/packages/lib', consistency: '' }, + darwin: { repoPath: '/Users/user/repo', gitConfigFile: '/Users/user/repo/.git/config', gitFile: '/Users/user/repo/.git', gitdir: 'gitdir: ../main-repo/.git/worktrees/repo', mainRepoGitPath: '/Users/user/main-repo/.git', subfolderPath: '/Users/user/repo/packages/lib', consistency: ',consistency=consistent' }, + win32: { repoPath: 'C:\\Users\\user\\repo', gitConfigFile: 'C:\\Users\\user\\repo\\.git\\config', gitFile: 'C:\\Users\\user\\repo\\.git', gitdir: 'gitdir: ../main-repo/.git/worktrees/repo', mainRepoGitPath: 'C:\\Users\\user\\main-repo\\.git', subfolderPath: 'C:\\Users\\user\\repo\\packages\\lib', consistency: ',consistency=consistent' }, + }[platform]; + + const cliHost = createMockCLIHost({ + platform, + files: { + [p.gitConfigFile]: '[core]', + [p.gitFile]: p.gitdir + }, + useFileHost: true + }); + const workspace = createWorkspace(p.subfolderPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + {}, + true, + true, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/workspaces/repo/packages/lib'); + assert.strictEqual(result.additionalMountString, `type=bind,source=${p.mainRepoGitPath},target=/workspaces/main-repo/.git${p.consistency}`); + }); + + }); + + describe('config overrides', function () { + + it('should use workspaceFolder from config when provided', async () => { + const p = { + linux: { projectPath: '/home/user/project' }, + darwin: { projectPath: '/Users/user/project' }, + win32: { projectPath: 'C:\\Users\\user\\project' }, + }[platform]; + + const cliHost = createMockCLIHost({ platform }); + const workspace = createWorkspace(p.projectPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + { workspaceFolder: '/custom/path' }, + false, + false, + nullLog + ); + + assert.strictEqual(result.workspaceFolder, '/custom/path'); + }); + + it('should use workspaceMount from config when provided', async () => { + const p = { + linux: { projectPath: '/home/user/project' }, + darwin: { projectPath: '/Users/user/project' }, + win32: { projectPath: 'C:\\Users\\user\\project' }, + }[platform]; + + const cliHost = createMockCLIHost({ platform }); + const workspace = createWorkspace(p.projectPath); + + const result = await getWorkspaceConfiguration( + cliHost, + workspace, + { workspaceMount: 'type=bind,source=/custom,target=/workspace' }, + false, + false, + nullLog + ); + + assert.strictEqual(result.workspaceMount, 'type=bind,source=/custom,target=/workspace'); + }); + + }); + + }); + } + +}); diff --git a/yarn.lock b/yarn.lock index bfd781f71..c70431cf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,20 +2,6 @@ # yarn lockfile v1 -"@babel/code-frame@^7.0.0": - version "7.26.2" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" - integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== - dependencies: - "@babel/helper-validator-identifier" "^7.25.9" - js-tokens "^4.0.0" - picocolors "^1.0.0" - -"@babel/helper-validator-identifier@^7.25.9": - version "7.25.9" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz#24b64e2c3ec7cd3b3c547729b8d16871f22cbdc7" - integrity sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ== - "@cspotcode/source-map-support@^0.8.0": version "0.8.1" resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" @@ -23,174 +9,183 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@esbuild/aix-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz#ee6b7163a13528e099ecf562b972f2bcebe0aa97" - integrity sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw== - -"@esbuild/android-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz#115fc76631e82dd06811bfaf2db0d4979c16e2cb" - integrity sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg== - -"@esbuild/android-arm@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.10.tgz#8d5811912da77f615398611e5bbc1333fe321aa9" - integrity sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w== - -"@esbuild/android-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.10.tgz#e3e96516b2d50d74105bb92594c473e30ddc16b1" - integrity sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg== - -"@esbuild/darwin-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz#6af6bb1d05887dac515de1b162b59dc71212ed76" - integrity sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA== - -"@esbuild/darwin-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz#99ae82347fbd336fc2d28ffd4f05694e6e5b723d" - integrity sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg== - -"@esbuild/freebsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz#0c6d5558a6322b0bdb17f7025c19bd7d2359437d" - integrity sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg== - -"@esbuild/freebsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz#8c35873fab8c0857a75300a3dcce4324ca0b9844" - integrity sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA== - -"@esbuild/linux-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz#3edc2f87b889a15b4cedaf65f498c2bed7b16b90" - integrity sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ== - -"@esbuild/linux-arm@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz#86501cfdfb3d110176d80c41b27ed4611471cde7" - integrity sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg== - -"@esbuild/linux-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz#e6589877876142537c6864680cd5d26a622b9d97" - integrity sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ== - -"@esbuild/linux-loong64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz#11119e18781f136d8083ea10eb6be73db7532de8" - integrity sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg== - -"@esbuild/linux-mips64el@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz#3052f5436b0c0c67a25658d5fc87f045e7def9e6" - integrity sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA== - -"@esbuild/linux-ppc64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz#2f098920ee5be2ce799f35e367b28709925a8744" - integrity sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA== - -"@esbuild/linux-riscv64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz#fa51d7fd0a22a62b51b4b94b405a3198cf7405dd" - integrity sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA== - -"@esbuild/linux-s390x@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz#a27642e36fc282748fdb38954bd3ef4f85791e8a" - integrity sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew== - -"@esbuild/linux-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz#9d9b09c0033d17529570ced6d813f98315dfe4e9" - integrity sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA== - -"@esbuild/netbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz#25c09a659c97e8af19e3f2afd1c9190435802151" - integrity sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A== - -"@esbuild/netbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz#7fa5f6ffc19be3a0f6f5fd32c90df3dc2506937a" - integrity sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig== - -"@esbuild/openbsd-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz#8faa6aa1afca0c6d024398321d6cb1c18e72a1c3" - integrity sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw== - -"@esbuild/openbsd-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz#a42979b016f29559a8453d32440d3c8cd420af5e" - integrity sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw== - -"@esbuild/openharmony-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz#fd87bfeadd7eeb3aa384bbba907459ffa3197cb1" - integrity sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag== - -"@esbuild/sunos-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz#3a18f590e36cb78ae7397976b760b2b8c74407f4" - integrity sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ== - -"@esbuild/win32-arm64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz#e71741a251e3fd971408827a529d2325551f530c" - integrity sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw== - -"@esbuild/win32-ia32@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz#c6f010b5d3b943d8901a0c87ea55f93b8b54bf94" - integrity sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw== - -"@esbuild/win32-x64@0.25.10": - version "0.25.10" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz#e4b3e255a1b4aea84f6e1d2ae0b73f826c3785bd" - integrity sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw== - -"@eslint-community/eslint-utils@^4.2.0": - version "4.4.1" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" - integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== +"@esbuild/aix-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz#815b39267f9bffd3407ea6c376ac32946e24f8d2" + integrity sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg== + +"@esbuild/android-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz#19b882408829ad8e12b10aff2840711b2da361e8" + integrity sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg== + +"@esbuild/android-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.27.3.tgz#90be58de27915efa27b767fcbdb37a4470627d7b" + integrity sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA== + +"@esbuild/android-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.27.3.tgz#d7dcc976f16e01a9aaa2f9b938fbec7389f895ac" + integrity sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ== + +"@esbuild/darwin-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz#9f6cac72b3a8532298a6a4493ed639a8988e8abd" + integrity sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg== + +"@esbuild/darwin-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz#ac61d645faa37fd650340f1866b0812e1fb14d6a" + integrity sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg== + +"@esbuild/freebsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz#b8625689d73cf1830fe58c39051acdc12474ea1b" + integrity sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w== + +"@esbuild/freebsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz#07be7dd3c9d42fe0eccd2ab9f9ded780bc53bead" + integrity sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA== + +"@esbuild/linux-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz#bf31918fe5c798586460d2b3d6c46ed2c01ca0b6" + integrity sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg== + +"@esbuild/linux-arm@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz#28493ee46abec1dc3f500223cd9f8d2df08f9d11" + integrity sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw== + +"@esbuild/linux-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz#750752a8b30b43647402561eea764d0a41d0ee29" + integrity sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg== + +"@esbuild/linux-loong64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz#a5a92813a04e71198c50f05adfaf18fc1e95b9ed" + integrity sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA== + +"@esbuild/linux-mips64el@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz#deb45d7fd2d2161eadf1fbc593637ed766d50bb1" + integrity sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw== + +"@esbuild/linux-ppc64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz#6f39ae0b8c4d3d2d61a65b26df79f6e12a1c3d78" + integrity sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA== + +"@esbuild/linux-riscv64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz#4c5c19c3916612ec8e3915187030b9df0b955c1d" + integrity sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ== + +"@esbuild/linux-s390x@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz#9ed17b3198fa08ad5ccaa9e74f6c0aff7ad0156d" + integrity sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw== + +"@esbuild/linux-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz#12383dcbf71b7cf6513e58b4b08d95a710bf52a5" + integrity sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA== + +"@esbuild/netbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz#dd0cb2fa543205fcd931df44f4786bfcce6df7d7" + integrity sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA== + +"@esbuild/netbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz#028ad1807a8e03e155153b2d025b506c3787354b" + integrity sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA== + +"@esbuild/openbsd-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz#e3c16ff3490c9b59b969fffca87f350ffc0e2af5" + integrity sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw== + +"@esbuild/openbsd-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz#c5a4693fcb03d1cbecbf8b422422468dfc0d2a8b" + integrity sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ== + +"@esbuild/openharmony-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz#082082444f12db564a0775a41e1991c0e125055e" + integrity sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g== + +"@esbuild/sunos-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz#5ab036c53f929e8405c4e96e865a424160a1b537" + integrity sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA== + +"@esbuild/win32-arm64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz#38de700ef4b960a0045370c171794526e589862e" + integrity sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA== + +"@esbuild/win32-ia32@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz#451b93dc03ec5d4f38619e6cd64d9f9eff06f55c" + integrity sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q== + +"@esbuild/win32-x64@0.27.3": + version "0.27.3" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz#0eaf705c941a218a43dba8e09f1df1d6cd2f1f17" + integrity sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA== + +"@eslint-community/eslint-utils@^4.8.0", "@eslint-community/eslint-utils@^4.9.1": + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: eslint-visitor-keys "^3.4.3" -"@eslint-community/eslint-utils@^4.7.0": - version "4.7.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" - integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== +"@eslint-community/regexpp@^4.12.2": + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== + +"@eslint/config-array@^0.23.2": + version "0.23.2" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.23.2.tgz#db85beeff7facc685a5775caacb1c845669b9470" + integrity sha512-YF+fE6LV4v5MGWRGj7G404/OZzGNepVF8fxk7jqmqo3lrza7a0uUcDnROGRBG1WFC1omYUS/Wp1f42i0M+3Q3A== dependencies: - eslint-visitor-keys "^3.4.3" + "@eslint/object-schema" "^3.0.2" + debug "^4.3.1" + minimatch "^10.2.1" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.12.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" - integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== +"@eslint/config-helpers@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d" + integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ== + dependencies: + "@eslint/core" "^1.1.0" -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/core@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d" + integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw== 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" + "@types/json-schema" "^7.0.15" -"@eslint/js@8.57.1": - version "8.57.1" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2" - integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@eslint/object-schema@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-3.0.2.tgz#c59c6a94aa4b428ed7f1615b6a4495c0a21f7a22" + integrity sha512-HOy56KJt48Bx8KmJ+XGQNSUMT/6dZee/M54XyUyuvTvPXJmsERRvBchsUVx1UMe1WwIH49XLAczNC7V2INsuUw== + +"@eslint/plugin-kit@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz#e0cb12ec66719cb2211ad36499fb516f2a63899d" + integrity sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ== + dependencies: + "@eslint/core" "^1.1.0" + levn "^0.4.1" "@gulpjs/to-absolute-glob@^4.0.0": version "4.0.0" @@ -199,24 +194,28 @@ dependencies: is-negated-glob "^1.0.0" -"@humanwhocodes/config-array@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.13.0.tgz#fb907624df3256d04b9aa2df50d7aa97ec648748" - integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw== +"@humanfs/core@^0.19.1": + version "0.19.1" + resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.1.tgz#17c55ca7d426733fe3c561906b8173c336b40a77" + integrity sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA== + +"@humanfs/node@^0.16.6": + version "0.16.7" + resolved "https://registry.yarnpkg.com/@humanfs/node/-/node-0.16.7.tgz#822cb7b3a12c5a240a24f621b5a2413e27a45f26" + integrity sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ== dependencies: - "@humanwhocodes/object-schema" "^2.0.3" - debug "^4.3.1" - minimatch "^3.0.5" + "@humanfs/core" "^0.19.1" + "@humanwhocodes/retry" "^0.4.0" "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== -"@humanwhocodes/object-schema@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" - integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== +"@humanwhocodes/retry@^0.4.0", "@humanwhocodes/retry@^0.4.2": + version "0.4.3" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" + integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -230,15 +229,22 @@ wrap-ansi "^8.1.0" wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" +"@isaacs/fs-minipass@^4.0.0": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32" + integrity sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w== + dependencies: + minipass "^7.0.4" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -248,42 +254,22 @@ "@jridgewell/resolve-uri" "^3.0.3" "@jridgewell/sourcemap-codec" "^1.4.10" -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - "@pkgjs/parseargs@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== -"@stylistic/eslint-plugin@^3.0.1": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-3.1.0.tgz#a9f655c518f76bfc5feb46b467d0f06e511b289d" - integrity sha512-pA6VOrOqk0+S8toJYhQGv2MWpQQR0QpeUo9AhNkC49Y26nxBQ/nH1rta9bUU1rPw2fJ1zZEMV5oCX5AazT7J2g== +"@stylistic/eslint-plugin@^5.10.0": + version "5.10.0" + resolved "https://registry.yarnpkg.com/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz#471bbd9f7a27ceaac4a217e7f5b3890855e5640c" + integrity sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ== dependencies: - "@typescript-eslint/utils" "^8.13.0" - eslint-visitor-keys "^4.2.0" - espree "^10.3.0" + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/types" "^8.56.0" + eslint-visitor-keys "^4.2.1" + espree "^10.4.0" estraverse "^5.3.0" - picomatch "^4.0.2" + picomatch "^4.0.3" "@tootallnate/quickjs-emscripten@^0.23.0": version "0.23.0" @@ -291,9 +277,9 @@ integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== "@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + version "1.0.12" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -315,12 +301,15 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.20.tgz#cb291577ed342ca92600430841a00329ba05cecc" integrity sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ== -"@types/chalk@^2.2.4": - version "2.2.4" - resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.4.tgz#32cd215134f191402f4909b250c28efa1f3a9c01" - integrity sha512-pb/QoGqtCpH2famSp72qEsXkNzcErlVmiXlQ/ww+5AddD8TmmYS7EWg5T20YiNCAiTgs8pMf2G8SJG5h/ER1ZQ== - dependencies: - chalk "*" +"@types/esrecurse@^4.3.1": + version "4.3.1" + resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" + integrity sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw== + +"@types/estree@^1.0.6", "@types/estree@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" + integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== "@types/follow-redirects@^1.14.4": version "1.14.4" @@ -334,7 +323,7 @@ resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.9.tgz#cd82382c4f902fed9691a2ed79ec68c5898af4c2" integrity sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg== -"@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -352,18 +341,18 @@ "@types/node" "*" "@types/node@*": - version "22.13.9" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca" - integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw== + version "25.3.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.3.5.tgz#beccb5915561f7a9970ace547ad44d6cdbf39b46" + integrity sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA== dependencies: - undici-types "~6.20.0" + undici-types "~7.18.0" -"@types/node@^18.19.127": - version "18.19.127" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.127.tgz#7c2e47fa79ad7486134700514d4a975c4607f09d" - integrity sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA== +"@types/node@^20.19.37": + version "20.19.37" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.19.37.tgz#b4fb4033408dd97becce63ec932c9ec57a9e2919" + integrity sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw== dependencies: - undici-types "~5.26.4" + undici-types "~6.21.0" "@types/pull-stream@^3.6.7": version "3.6.7" @@ -377,7 +366,7 @@ dependencies: "@types/node" "*" -"@types/semver@^7.3.12", "@types/semver@^7.5.8": +"@types/semver@^7.7.1": version "7.7.1" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.7.1.tgz#3ce3af1a5524ef327d2da9e4fd8b6d95c8d70528" integrity sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA== @@ -387,14 +376,6 @@ resolved "https://registry.yarnpkg.com/@types/shell-quote/-/shell-quote-1.7.5.tgz#6db4704742d307cd6d604e124e3ad6cd5ed943f3" integrity sha512-+UE8GAGRPbJVQDdxi16dgadcBfQ+KG2vgZhV1+3A1XmHbmwcdwhCUwIdy+d3pAGrbvgRoVSjeI9vOWyq376Yzw== -"@types/tar@^6.1.13": - version "6.1.13" - resolved "https://registry.yarnpkg.com/@types/tar/-/tar-6.1.13.tgz#9b5801c02175344101b4b91086ab2bbc8e93a9b6" - integrity sha512-IznnlmU5f4WcGTh2ltRu/Ijpmk8wiWXfF0VA4s+HPjHZgvFggk1YaIkbo5krX/zUCzWF8N/l4+W/LNxnvAJ8nw== - dependencies: - "@types/node" "*" - minipass "^4.0.0" - "@types/text-table@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@types/text-table/-/text-table-0.2.5.tgz#f9c609b81c943e9fc8d73ef82ad2f2a78be5f53b" @@ -405,245 +386,152 @@ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" integrity sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ== -"@types/yargs@^17.0.33": - version "17.0.33" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.33.tgz#8c32303da83eec050a84b3c7ae7b9f922d13e32d" - integrity sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA== +"@types/yargs@^17.0.35": + version "17.0.35" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.35.tgz#07013e46aa4d7d7d50a49e15604c1c5340d4eb24" + integrity sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg== dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^8.26.0": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz#011a2b5913d297b3d9d77f64fb78575bab01a1b3" - integrity sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw== - dependencies: - "@eslint-community/regexpp" "^4.10.0" - "@typescript-eslint/scope-manager" "8.44.1" - "@typescript-eslint/type-utils" "8.44.1" - "@typescript-eslint/utils" "8.44.1" - "@typescript-eslint/visitor-keys" "8.44.1" - graphemer "^1.4.0" - ignore "^7.0.0" +"@typescript-eslint/eslint-plugin@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz#b1ce606d87221daec571e293009675992f0aae76" + integrity sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A== + dependencies: + "@eslint-community/regexpp" "^4.12.2" + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/type-utils" "8.56.1" + "@typescript-eslint/utils" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + ignore "^7.0.5" natural-compare "^1.4.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/experimental-utils@^5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741" - integrity sha512-RTXpeB3eMkpoclG3ZHft6vG/Z30azNHuqY6wKPBHlVMZFuEvrtlEDe8gMqDb+SO+9hjC/pLekeSCryf9vMZlCw== - dependencies: - "@typescript-eslint/utils" "5.62.0" - -"@typescript-eslint/parser@^8.26.0": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.44.1.tgz#d4c85791389462823596ad46e2b90d34845e05eb" - integrity sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw== - dependencies: - "@typescript-eslint/scope-manager" "8.44.1" - "@typescript-eslint/types" "8.44.1" - "@typescript-eslint/typescript-estree" "8.44.1" - "@typescript-eslint/visitor-keys" "8.44.1" - debug "^4.3.4" - -"@typescript-eslint/project-service@8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.44.1.tgz#1bccd9796d25032b190f355f55c5fde061158abb" - integrity sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA== - dependencies: - "@typescript-eslint/tsconfig-utils" "^8.44.1" - "@typescript-eslint/types" "^8.44.1" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - -"@typescript-eslint/scope-manager@8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz#31c27f92e4aed8d0f4d6fe2b9e5187d1d8797bd7" - integrity sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg== - dependencies: - "@typescript-eslint/types" "8.44.1" - "@typescript-eslint/visitor-keys" "8.44.1" - -"@typescript-eslint/tsconfig-utils@8.44.1", "@typescript-eslint/tsconfig-utils@^8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz#e1d9d047078fac37d3e638484ab3b56215963342" - integrity sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ== - -"@typescript-eslint/type-utils@8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz#be9d31e0f911d17ee8ac99921bb74cf1f9df3906" - integrity sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g== - dependencies: - "@typescript-eslint/types" "8.44.1" - "@typescript-eslint/typescript-estree" "8.44.1" - "@typescript-eslint/utils" "8.44.1" - debug "^4.3.4" - ts-api-utils "^2.1.0" - -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== - -"@typescript-eslint/types@8.44.1", "@typescript-eslint/types@^8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.44.1.tgz#85d1cad1290a003ff60420388797e85d1c3f76ff" - integrity sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ== - -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== - dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/typescript-estree@8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz#4f17650e5adabecfcc13cd8c517937a4ef5cd424" - integrity sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A== - dependencies: - "@typescript-eslint/project-service" "8.44.1" - "@typescript-eslint/tsconfig-utils" "8.44.1" - "@typescript-eslint/types" "8.44.1" - "@typescript-eslint/visitor-keys" "8.44.1" - debug "^4.3.4" - fast-glob "^3.3.2" - is-glob "^4.0.3" - minimatch "^9.0.4" - semver "^7.6.0" - ts-api-utils "^2.1.0" - -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/utils@8.44.1", "@typescript-eslint/utils@^8.13.0": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.44.1.tgz#f23d48eb90791a821dc17d4f67bb96faeb75d63d" - integrity sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg== - dependencies: - "@eslint-community/eslint-utils" "^4.7.0" - "@typescript-eslint/scope-manager" "8.44.1" - "@typescript-eslint/types" "8.44.1" - "@typescript-eslint/typescript-estree" "8.44.1" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" - -"@typescript-eslint/visitor-keys@8.44.1": - version "8.44.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz#1d96197a7fcceaba647b3bd6a8594df8dc4deb5a" - integrity sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw== - dependencies: - "@typescript-eslint/types" "8.44.1" - eslint-visitor-keys "^4.2.1" - -"@ungap/structured-clone@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" - integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== - -acorn-jsx@^5.2.0, acorn-jsx@^5.3.2: + ts-api-utils "^2.4.0" + +"@typescript-eslint/parser@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.56.1.tgz#21d13b3d456ffb08614c1d68bb9a4f8d9237cdc7" + integrity sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg== + dependencies: + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + debug "^4.4.3" + +"@typescript-eslint/project-service@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.56.1.tgz#65c8d645f028b927bfc4928593b54e2ecd809244" + integrity sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ== + dependencies: + "@typescript-eslint/tsconfig-utils" "^8.56.1" + "@typescript-eslint/types" "^8.56.1" + debug "^4.4.3" + +"@typescript-eslint/scope-manager@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz#254df93b5789a871351335dd23e20bc164060f24" + integrity sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w== + dependencies: + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + +"@typescript-eslint/tsconfig-utils@8.56.1", "@typescript-eslint/tsconfig-utils@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz#1afa830b0fada5865ddcabdc993b790114a879b7" + integrity sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ== + +"@typescript-eslint/type-utils@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz#7a6c4fabf225d674644931e004302cbbdd2f2e24" + integrity sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg== + dependencies: + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + "@typescript-eslint/utils" "8.56.1" + debug "^4.4.3" + ts-api-utils "^2.4.0" + +"@typescript-eslint/types@8.56.1", "@typescript-eslint/types@^8.56.0", "@typescript-eslint/types@^8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.56.1.tgz#975e5942bf54895291337c91b9191f6eb0632ab9" + integrity sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw== + +"@typescript-eslint/typescript-estree@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz#3b9e57d8129a860c50864c42188f761bdef3eab0" + integrity sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg== + dependencies: + "@typescript-eslint/project-service" "8.56.1" + "@typescript-eslint/tsconfig-utils" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/visitor-keys" "8.56.1" + debug "^4.4.3" + minimatch "^10.2.2" + semver "^7.7.3" + tinyglobby "^0.2.15" + ts-api-utils "^2.4.0" + +"@typescript-eslint/utils@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.56.1.tgz#5a86acaf9f1b4c4a85a42effb217f73059f6deb7" + integrity sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA== + dependencies: + "@eslint-community/eslint-utils" "^4.9.1" + "@typescript-eslint/scope-manager" "8.56.1" + "@typescript-eslint/types" "8.56.1" + "@typescript-eslint/typescript-estree" "8.56.1" + +"@typescript-eslint/visitor-keys@8.56.1": + version "8.56.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz#50e03475c33a42d123dc99e63acf1841c0231f87" + integrity sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw== + dependencies: + "@typescript-eslint/types" "8.56.1" + eslint-visitor-keys "^5.0.0" + +acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.3.4" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" - integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== + version "8.3.5" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.5.tgz#8a6b8ca8fc5b34685af15dabb44118663c296496" + integrity sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw== dependencies: acorn "^8.11.0" -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.11.0, acorn@^8.14.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.14.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb" - integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== +acorn@^8.11.0, acorn@^8.15.0, acorn@^8.16.0, acorn@^8.4.1: + version "8.16.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.16.0.tgz#4ce79c89be40afe7afe8f3adb902a1f1ce9ac08a" + integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== agent-base@^7.1.0, agent-base@^7.1.2: - version "7.1.3" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1" - integrity sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw== + version "7.1.4" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" + integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== -ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== +ajv@^6.14.0: + version "6.14.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.14.0.tgz#fd067713e228210636ebb08c60bd3765d6dbe73a" + integrity sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw== 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" -ansi-colors@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" - integrity sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA== - dependencies: - ansi-wrap "^0.1.0" - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-gray@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" - integrity sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw== - dependencies: - ansi-wrap "0.1.0" - -ansi-regex@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.1.tgz#164daac87ab2d6f6db3a29875e2d1766582dabed" - integrity sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g== - ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== +ansi-regex@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.2.2.tgz#60216eea464d864597ce2832000738a0589650c1" + integrity sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg== -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -658,14 +546,9 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: color-convert "^2.0.1" ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - -ansi-wrap@0.1.0, ansi-wrap@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" - integrity sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw== + version "6.2.3" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.3.tgz#c044d5dcc521a076413472597a1acb1f103c4041" + integrity sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg== anymatch@^3.1.3: version "3.1.3" @@ -680,28 +563,11 @@ arg@^4.1.0: resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== - array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz#384d12a37295aec3769ab022ad323a18a51ccf8b" @@ -710,21 +576,6 @@ array-buffer-byte-length@^1.0.1, array-buffer-byte-length@^1.0.2: call-bound "^1.0.3" is-array-buffer "^3.0.5" -array-differ@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-4.0.0.tgz#aa3c891c653523290c880022f45b06a42051b026" - integrity sha512-Q6VPTLMsmXZ47ENG3V+wQyZS1ZxXMxFyYzA+Z/GMrJ6yIutAIEf9wTyroTzmGjNfox9/h3GdGBCVh43GVFx4Uw== - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array-union@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-3.0.1.tgz#da52630d327f8b88cfbfb57728e2af5cd9b6b975" - integrity sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw== - arraybuffer.prototype.slice@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz#9d760d84dbdd06d0cbf92c8849615a1a7ab3183c" @@ -743,11 +594,6 @@ assertion-error@^1.1.0: resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== - ast-types@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" @@ -755,11 +601,6 @@ ast-types@^0.13.4: dependencies: tslib "^2.0.1" -astral-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" - integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== - async-function@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-function/-/async-function-1.0.0.tgz#509c9fca60eaf85034c6829838188e4e4c8ffb2b" @@ -773,19 +614,24 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" b4a@^1.6.4: - version "1.6.7" - resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.7.tgz#a99587d4ebbfbd5a6e3b21bdb5d5fa385767abe4" - integrity sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg== + version "1.8.0" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.8.0.tgz#1ca3ba0edc9469aaabef5647e769a83d50180b1a" + integrity sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg== balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -bare-events@^2.2.0: - version "2.5.4" - resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.5.4.tgz#16143d435e1ed9eafd1ab85f12b89b3357a41745" - integrity sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA== +balanced-match@^4.0.2: + version "4.0.4" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" + integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== + +bare-events@^2.7.0: + version "2.8.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.8.2.tgz#7b3e10bd8e1fc80daf38bb516921678f566ab89f" + integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== base64-js@^1.3.1: version "1.5.1" @@ -793,9 +639,9 @@ base64-js@^1.3.1: integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== basic-ftp@^5.0.2: - version "5.0.5" - resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.0.5.tgz#14a474f5fffecca1f4f406f1c26b18f800225ac0" - integrity sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg== + version "5.3.1" + resolved "https://registry.yarnpkg.com/basic-ftp/-/basic-ftp-5.3.1.tgz#3148ee9af43c0522514a4f973fecb1d3cbb6d71e" + integrity sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw== bl@^5.0.0: version "5.1.0" @@ -807,26 +653,26 @@ bl@^5.0.0: readable-stream "^3.4.0" brace-expansion@^1.1.7: - version "1.1.12" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" - integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== + version "1.1.13" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6" + integrity "sha1-03h1wB3J7/mI3UnREqV8tntU7+Y= sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==" dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -brace-expansion@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" - integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== +brace-expansion@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.3.tgz#0493338bdd58e319b1039c67cf7ee439892c01d9" + integrity "sha1-BJMzi91Y4xmxA5xnz37kOYksAdk= sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==" dependencies: balanced-match "^1.0.0" -braces@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" - integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== +brace-expansion@^5.0.2: + version "5.0.5" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" + integrity "sha1-3MOjcRa3nz4bRtuZTO1dVw6TD9s= sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==" dependencies: - fill-range "^7.1.1" + balanced-match "^4.0.2" browser-stdout@^1.3.1: version "1.3.1" @@ -859,7 +705,7 @@ call-bind@^1.0.7, call-bind@^1.0.8: get-intrinsic "^1.2.4" set-function-length "^1.2.2" -call-bound@^1.0.2, call-bound@^1.0.3: +call-bound@^1.0.2, call-bound@^1.0.3, call-bound@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== @@ -867,11 +713,6 @@ call-bound@^1.0.2, call-bound@^1.0.3: call-bind-apply-helpers "^1.0.2" get-intrinsic "^1.3.0" -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - camelcase@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" @@ -890,12 +731,7 @@ chai@^4.5.0: pathval "^1.1.1" type-detect "^4.1.0" -chalk@*, chalk@^5.4.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" - integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== - -chalk@^2.1.0, chalk@^2.4.1: +chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -904,7 +740,7 @@ chalk@^2.1.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -912,10 +748,10 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== +chalk@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== check-error@^1.0.3: version "1.0.3" @@ -931,22 +767,10 @@ chokidar@^4.0.1: dependencies: readdirp "^4.0.1" -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-width@^3.0.0: +chownr@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + resolved "https://registry.yarnpkg.com/chownr/-/chownr-3.0.0.tgz#9855e64ecd240a9cc4267ce8a4aa5d24a1da15e4" + integrity sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g== cliui@^7.0.2: version "7.0.4" @@ -995,11 +819,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-support@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" - integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== - commander@^2.19.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -1054,7 +873,7 @@ cross-spawn@^6.0.5: shebang-command "^1.2.0" which "^1.2.9" -cross-spawn@^7.0.2, cross-spawn@^7.0.6: +cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1095,10 +914,10 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -debug@4, debug@^4.0.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: - version "4.4.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" - integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== +debug@4, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -1114,7 +933,7 @@ deep-eql@^4.1.3: dependencies: type-detect "^4.0.0" -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -1147,29 +966,15 @@ degenerator@^5.0.0: esprima "^4.0.1" diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== diff@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dunder-proto@^1.0.0, dunder-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" @@ -1199,11 +1004,6 @@ editorconfig@^0.15.0: semver "^5.6.0" sigmund "^1.0.1" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" @@ -1215,33 +1015,33 @@ emoji-regex@^9.2.2: integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + version "1.3.4" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414" + integrity sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ== dependencies: is-arrayish "^0.2.1" es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.9: - version "1.23.9" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.9.tgz#5b45994b7de78dada5c1bebf1379646b32b9d606" - integrity sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA== + version "1.24.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.24.1.tgz#f0c131ed5ea1bb2411134a8dd94def09c46c7899" + integrity sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw== dependencies: array-buffer-byte-length "^1.0.2" arraybuffer.prototype.slice "^1.0.4" available-typed-arrays "^1.0.7" call-bind "^1.0.8" - call-bound "^1.0.3" + call-bound "^1.0.4" data-view-buffer "^1.0.2" data-view-byte-length "^1.0.2" data-view-byte-offset "^1.0.1" es-define-property "^1.0.1" es-errors "^1.3.0" - es-object-atoms "^1.0.0" + es-object-atoms "^1.1.1" es-set-tostringtag "^2.1.0" es-to-primitive "^1.3.0" function.prototype.name "^1.1.8" - get-intrinsic "^1.2.7" - get-proto "^1.0.0" + get-intrinsic "^1.3.0" + get-proto "^1.0.1" get-symbol-description "^1.1.0" globalthis "^1.0.4" gopd "^1.2.0" @@ -1253,21 +1053,24 @@ es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.9: is-array-buffer "^3.0.5" is-callable "^1.2.7" is-data-view "^1.0.2" + is-negative-zero "^2.0.3" is-regex "^1.2.1" + is-set "^2.0.3" is-shared-array-buffer "^1.0.4" is-string "^1.1.1" is-typed-array "^1.1.15" - is-weakref "^1.1.0" + is-weakref "^1.1.1" math-intrinsics "^1.1.0" - object-inspect "^1.13.3" + object-inspect "^1.13.4" object-keys "^1.1.1" object.assign "^4.1.7" own-keys "^1.0.1" - regexp.prototype.flags "^1.5.3" + regexp.prototype.flags "^1.5.4" safe-array-concat "^1.1.3" safe-push-apply "^1.0.0" safe-regex-test "^1.1.0" set-proto "^1.0.0" + stop-iteration-iterator "^1.1.0" string.prototype.trim "^1.2.10" string.prototype.trimend "^1.0.9" string.prototype.trimstart "^1.0.8" @@ -1276,7 +1079,7 @@ es-abstract@^1.23.2, es-abstract@^1.23.5, es-abstract@^1.23.9: typed-array-byte-offset "^1.0.4" typed-array-length "^1.0.7" unbox-primitive "^1.1.0" - which-typed-array "^1.1.18" + which-typed-array "^1.1.19" es-define-property@^1.0.0, es-define-property@^1.0.1: version "1.0.1" @@ -1314,37 +1117,37 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" -esbuild@^0.25.0: - version "0.25.10" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.10.tgz#37f5aa5cd14500f141be121c01b096ca83ac34a9" - integrity sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ== +esbuild@^0.27.3: + version "0.27.3" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.27.3.tgz#5859ca8e70a3af956b26895ce4954d7e73bd27a8" + integrity sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg== optionalDependencies: - "@esbuild/aix-ppc64" "0.25.10" - "@esbuild/android-arm" "0.25.10" - "@esbuild/android-arm64" "0.25.10" - "@esbuild/android-x64" "0.25.10" - "@esbuild/darwin-arm64" "0.25.10" - "@esbuild/darwin-x64" "0.25.10" - "@esbuild/freebsd-arm64" "0.25.10" - "@esbuild/freebsd-x64" "0.25.10" - "@esbuild/linux-arm" "0.25.10" - "@esbuild/linux-arm64" "0.25.10" - "@esbuild/linux-ia32" "0.25.10" - "@esbuild/linux-loong64" "0.25.10" - "@esbuild/linux-mips64el" "0.25.10" - "@esbuild/linux-ppc64" "0.25.10" - "@esbuild/linux-riscv64" "0.25.10" - "@esbuild/linux-s390x" "0.25.10" - "@esbuild/linux-x64" "0.25.10" - "@esbuild/netbsd-arm64" "0.25.10" - "@esbuild/netbsd-x64" "0.25.10" - "@esbuild/openbsd-arm64" "0.25.10" - "@esbuild/openbsd-x64" "0.25.10" - "@esbuild/openharmony-arm64" "0.25.10" - "@esbuild/sunos-x64" "0.25.10" - "@esbuild/win32-arm64" "0.25.10" - "@esbuild/win32-ia32" "0.25.10" - "@esbuild/win32-x64" "0.25.10" + "@esbuild/aix-ppc64" "0.27.3" + "@esbuild/android-arm" "0.27.3" + "@esbuild/android-arm64" "0.27.3" + "@esbuild/android-x64" "0.27.3" + "@esbuild/darwin-arm64" "0.27.3" + "@esbuild/darwin-x64" "0.27.3" + "@esbuild/freebsd-arm64" "0.27.3" + "@esbuild/freebsd-x64" "0.27.3" + "@esbuild/linux-arm" "0.27.3" + "@esbuild/linux-arm64" "0.27.3" + "@esbuild/linux-ia32" "0.27.3" + "@esbuild/linux-loong64" "0.27.3" + "@esbuild/linux-mips64el" "0.27.3" + "@esbuild/linux-ppc64" "0.27.3" + "@esbuild/linux-riscv64" "0.27.3" + "@esbuild/linux-s390x" "0.27.3" + "@esbuild/linux-x64" "0.27.3" + "@esbuild/netbsd-arm64" "0.27.3" + "@esbuild/netbsd-x64" "0.27.3" + "@esbuild/openbsd-arm64" "0.27.3" + "@esbuild/openbsd-x64" "0.27.3" + "@esbuild/openharmony-arm64" "0.27.3" + "@esbuild/sunos-x64" "0.27.3" + "@esbuild/win32-arm64" "0.27.3" + "@esbuild/win32-ia32" "0.27.3" + "@esbuild/win32-x64" "0.27.3" escalade@^3.1.1: version "3.2.0" @@ -1372,172 +1175,94 @@ escodegen@^2.1.0: optionalDependencies: source-map "~0.6.1" -eslint-scope@^5.0.0, eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^9.1.1: + version "9.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-9.1.1.tgz#f6a209486e38bd28356b5feb07d445cc99c89967" + integrity sha512-GaUN0sWim5qc8KVErfPBWmc31LEsOkrUJbvJZV+xuL3u2phMUK4HIvXlWAakfC8W4nzlK+chPEAkYOYb5ZScIw== dependencies: + "@types/esrecurse" "^4.3.1" + "@types/estree" "^1.0.8" esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" - integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== - dependencies: - eslint-visitor-keys "^1.1.0" - -eslint-visitor-keys@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e" - integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ== - -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint-visitor-keys@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" - integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== - eslint-visitor-keys@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== -eslint@^6.0.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" - integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== - dependencies: - "@babel/code-frame" "^7.0.0" - ajv "^6.10.0" - chalk "^2.1.0" - cross-spawn "^6.0.5" - debug "^4.0.1" - doctrine "^3.0.0" - eslint-scope "^5.0.0" - eslint-utils "^1.4.3" - eslint-visitor-keys "^1.1.0" - espree "^6.1.2" - esquery "^1.0.1" - esutils "^2.0.2" - file-entry-cache "^5.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^5.0.0" - globals "^12.1.0" - ignore "^4.0.6" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - inquirer "^7.0.0" - is-glob "^4.0.0" - js-yaml "^3.13.1" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.3.0" - lodash "^4.17.14" - minimatch "^3.0.4" - mkdirp "^0.5.1" - natural-compare "^1.4.0" - optionator "^0.8.3" - progress "^2.0.0" - regexpp "^2.0.1" - semver "^6.1.2" - strip-ansi "^5.2.0" - strip-json-comments "^3.0.1" - table "^5.2.3" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -eslint@^8.57.0: - version "8.57.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.1.tgz#7df109654aba7e3bbe5c8eae533c5e461d3c6ca9" - integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== - dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.1" - "@humanwhocodes/config-array" "^0.13.0" +eslint-visitor-keys@^5.0.0, eslint-visitor-keys@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz#9e3c9489697824d2d4ce3a8ad12628f91e9f59be" + integrity sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA== + +eslint@^10.0.2: + version "10.0.2" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-10.0.2.tgz#1009263467591810320f2e1ad52b8a750d1acbab" + integrity sha512-uYixubwmqJZH+KLVYIVKY1JQt7tysXhtj21WSvjcSmU5SVNzMus1bgLe+pAt816yQ8opKfheVVoPLqvVMGejYw== + dependencies: + "@eslint-community/eslint-utils" "^4.8.0" + "@eslint-community/regexpp" "^4.12.2" + "@eslint/config-array" "^0.23.2" + "@eslint/config-helpers" "^0.5.2" + "@eslint/core" "^1.1.0" + "@eslint/plugin-kit" "^0.6.0" + "@humanfs/node" "^0.16.6" "@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" + "@humanwhocodes/retry" "^0.4.2" + "@types/estree" "^1.0.6" + ajv "^6.14.0" + cross-spawn "^7.0.6" 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" + eslint-scope "^9.1.1" + eslint-visitor-keys "^5.0.1" + espree "^11.1.1" + esquery "^1.7.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" 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" + minimatch "^10.2.1" natural-compare "^1.4.0" optionator "^0.9.3" - strip-ansi "^6.0.1" - text-table "^0.2.0" -espree@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-10.3.0.tgz#29267cf5b0cb98735b65e64ba07e0ed49d1eed8a" - integrity sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg== +espree@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.4.0.tgz#d54f4949d4629005a1fa168d937c3ff1f7e2a837" + integrity sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ== dependencies: - acorn "^8.14.0" + acorn "^8.15.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^4.2.0" - -espree@^6.1.2: - version "6.2.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" - integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw== - dependencies: - acorn "^7.1.1" - acorn-jsx "^5.2.0" - eslint-visitor-keys "^1.1.0" + eslint-visitor-keys "^4.2.1" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.1.1.tgz#866f6bc9ccccd6f28876b7a6463abb281b9cb847" + integrity sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ== dependencies: - acorn "^8.9.0" + acorn "^8.16.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^5.0.1" -esprima@^4.0.0, esprima@^4.0.1: +esprima@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1, esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== +esquery@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -1548,11 +1273,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -1576,32 +1296,12 @@ event-stream@^4.0.1: stream-combiner "^0.2.2" through "^2.3.8" -extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -fancy-log@^1.3.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" - integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== +events-universal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/events-universal/-/events-universal-1.0.1.tgz#b56a84fd611b6610e0a2d0f09f80fdf931e2dfe6" + integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== dependencies: - ansi-gray "^0.1.1" - color-support "^1.1.3" - parse-node-version "^1.0.0" - time-stamp "^1.0.0" + bare-events "^2.7.0" fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" @@ -1613,61 +1313,34 @@ fast-fifo@^1.3.2: resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== -fast-glob@^3.2.9, fast-glob@^3.3.2: - version "3.3.3" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" - integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== - 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.8" - fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -fastq@^1.13.0, fastq@^1.6.0: - version "1.19.1" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5" - integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== +fastq@^1.13.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -file-entry-cache@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" - integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== - dependencies: - flat-cache "^2.0.1" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" +fdir@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" + integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -fill-range@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" - integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - to-regex-range "^5.0.1" + flat-cache "^4.0.0" find-up@^5.0.0: version "5.0.0" @@ -1677,45 +1350,30 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" - integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== - dependencies: - flatted "^2.0.0" - rimraf "2.6.3" - write "1.0.3" - -flat-cache@^3.0.4: - version "3.2.0" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" - integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: flatted "^3.2.9" - keyv "^4.5.3" - rimraf "^3.0.2" + keyv "^4.5.4" flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== -flatted@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" - integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== - flatted@^3.2.9: - version "3.3.3" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" - integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity "sha1-9cI8EH8PN96NvfJPE3IrO5jVJyY= sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==" -follow-redirects@^1.15.9: - version "1.15.11" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.11.tgz#777d73d72a92f8ec4d2e410eb47352a56b8e8340" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== +follow-redirects@^1.15.11: + version "1.16.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.16.0.tgz#28474a159d3b9d11ef62050a14ed60e4df6d61bc" + integrity sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw== -for-each@^0.3.3: +for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.5.tgz#d650688027826920feeb0af747ee7b9421a41d47" integrity sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg== @@ -1735,13 +1393,6 @@ from@^0.1.7: resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - fs-mkdirp-stream@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz#1e82575c4023929ad35cf69269f84f1a8c973aa7" @@ -1772,16 +1423,16 @@ function.prototype.name@^1.1.6, function.prototype.name@^1.1.8: hasown "^2.0.2" is-callable "^1.2.7" -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generator-function@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/generator-function/-/generator-function-2.0.1.tgz#0e75dd410d1243687a0ba2e951b94eedb8f737a2" + integrity sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1808,7 +1459,7 @@ get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@ hasown "^2.0.2" math-intrinsics "^1.1.0" -get-proto@^1.0.0, get-proto@^1.0.1: +get-proto@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== @@ -1826,21 +1477,14 @@ get-symbol-description@^1.1.0: get-intrinsic "^1.2.6" get-uri@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.4.tgz#6daaee9e12f9759e19e55ba313956883ef50e0a7" - integrity sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ== + version "6.0.5" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.5.tgz#714892aa4a871db671abc5395e5e9447bc306a16" + integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg== dependencies: basic-ftp "^5.0.2" data-uri-to-buffer "^6.0.2" debug "^4.3.4" -glob-parent@^5.0.0, glob-parent@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - glob-parent@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" @@ -1862,10 +1506,10 @@ glob-stream@^8.0.3: normalize-path "^3.0.0" streamx "^2.12.5" -glob@^10.3.7, glob@^10.4.5: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== +glob@^10.4.5: + version "10.5.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c" + integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" @@ -1874,7 +1518,16 @@ glob@^10.3.7, glob@^10.4.5: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.0.5, glob@^7.1.3: +glob@^13.0.3: + version "13.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== + dependencies: + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" + +glob@^7.0.5: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1886,20 +1539,6 @@ glob@^7.0.5, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^12.1.0: - version "12.4.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" - integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== - dependencies: - type-fest "^0.8.1" - -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" - globalthis@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.4.tgz#7430ed3a975d97bfb59bcce41f5cabbafa651236" @@ -1908,18 +1547,6 @@ globalthis@^1.0.4: define-properties "^1.2.1" gopd "^1.0.1" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - gopd@^1.0.1, gopd@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" @@ -1930,31 +1557,6 @@ graceful-fs@^4.1.2, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.8 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== -graphemer@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" - integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== - -gulp-eslint@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gulp-eslint/-/gulp-eslint-6.0.0.tgz#7d402bb45f8a67652b868277011812057370a832" - integrity sha512-dCVPSh1sA+UVhn7JSQt7KEb4An2sQNbOdB3PA8UCfxsoPlAKjJHxYHGXdXC7eb+V1FAnilSFFqslPrq037l1ig== - dependencies: - eslint "^6.0.0" - fancy-log "^1.3.2" - plugin-error "^1.0.1" - -gulp-filter@^9.0.1: - version "9.0.1" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-9.0.1.tgz#26e9c578be732423028bebde750f764b7b8d6293" - integrity sha512-knVYL8h9bfYIeft3VokVTkuaWJkQJMrFCS3yVjZQC6BGg+1dZFoeUY++B9D2X4eFpeNTx9StWK0qnDby3NO3PA== - dependencies: - multimatch "^7.0.0" - plugin-error "^2.0.1" - slash "^5.1.0" - streamfilter "^3.0.0" - to-absolute-glob "^3.0.0" - has-bigints@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.1.0.tgz#28607e965ac967e03cd2a2c70a2636a1edad49fe" @@ -2029,13 +1631,6 @@ https-proxy-agent@^7.0.6: agent-base "^7.1.2" debug "4" -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -2048,28 +1643,15 @@ ieee754@^1.2.1: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.2.0: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== -ignore@^7.0.0: - version "7.0.4" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078" - integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" - integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" +ignore@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.5.tgz#4cb5f6cd7d4c7ab0365738c7aea888baa6d7efd9" + integrity sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg== imurmurhash@^0.1.4: version "0.1.4" @@ -2089,25 +1671,6 @@ inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inquirer@^7.0.0: - version "7.3.3" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" - integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.19" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.6.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - internal-slot@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.1.0.tgz#1eac91762947d2f7056bc838d93e13b2e9604961" @@ -2117,21 +1680,10 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" -ip-address@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-9.0.5.tgz#117a960819b08780c3bd1f14ef3c1cc1d3f3ea5a" - integrity sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g== - dependencies: - jsbn "1.1.0" - sprintf-js "^1.1.3" - -is-absolute@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576" - integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA== - dependencies: - is-relative "^1.0.0" - is-windows "^1.0.1" +ip-address@^10.0.1: + version "10.2.0" + resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-10.2.0.tgz#805fc178b20c518bd4c8548b24fe30892d7f3206" + integrity sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA== is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" @@ -2178,7 +1730,7 @@ is-callable@^1.2.7: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.16.0: +is-core-module@^2.16.1: version "2.16.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.16.1.tgz#2a98801a849f43e2add644fbb6bc6229b19a4ef4" integrity sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== @@ -2202,13 +1754,6 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -2221,27 +1766,23 @@ is-finalizationregistry@^1.1.0: dependencies: call-bound "^1.0.3" -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w== - is-fullwidth-code-point@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== is-generator-function@^1.0.10: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.0.tgz#bf3eeda931201394f57b5dba2800f91a238309ca" - integrity sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ== + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.1.2.tgz#ae3b61e3d5ea4e4839b90bad22b02335051a17d5" + integrity sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA== dependencies: - call-bound "^1.0.3" - get-proto "^1.0.0" + call-bound "^1.0.4" + generator-function "^2.0.0" + get-proto "^1.0.1" has-tostringtag "^1.0.2" safe-regex-test "^1.1.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -2258,6 +1799,11 @@ is-negated-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2" integrity sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug== +is-negative-zero@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" + integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== + is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -2266,11 +1812,6 @@ is-number-object@^1.1.1: call-bound "^1.0.3" has-tostringtag "^1.0.2" -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - is-path-inside@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" @@ -2281,13 +1822,6 @@ is-plain-obj@^2.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== -is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-regex@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.2.1.tgz#76d70a3ed10ef9be48eb577887d74205bf0cad22" @@ -2298,13 +1832,6 @@ is-regex@^1.2.1: has-tostringtag "^1.0.2" hasown "^2.0.2" -is-relative@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d" - integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA== - dependencies: - is-unc-path "^1.0.0" - is-set@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.3.tgz#8ab209ea424608141372ded6e0cb200ef1d9d01d" @@ -2341,13 +1868,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: dependencies: which-typed-array "^1.1.16" -is-unc-path@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d" - integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ== - dependencies: - unc-path-regex "^0.1.2" - is-unicode-supported@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" @@ -2363,7 +1883,7 @@ is-weakmap@^2.0.2: resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.2.tgz#bf72615d649dfe5f699079c54b83e47d1ae19cfd" integrity sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w== -is-weakref@^1.0.2, is-weakref@^1.1.0: +is-weakref@^1.0.2, is-weakref@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.1.1.tgz#eea430182be8d64174bd96bffbc46f21bf3f9293" integrity sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew== @@ -2378,11 +1898,6 @@ is-weakset@^2.0.3: call-bound "^1.0.3" get-intrinsic "^1.2.6" -is-windows@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -2403,11 +1918,6 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== - jackspeak@^3.1.2: version "3.4.3" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" @@ -2417,31 +1927,13 @@ jackspeak@^3.1.2: optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== +js-yaml@^4.1.0, js-yaml@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" -jsbn@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040" - integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== - json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -2467,7 +1959,7 @@ jsonc-parser@^3.3.1: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== -keyv@^4.5.3: +keyv@^4.5.4: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== @@ -2479,14 +1971,6 @@ lead@^4.0.0: resolved "https://registry.yarnpkg.com/lead/-/lead-4.0.0.tgz#5317a49effb0e7ec3a0c8fb9c1b24fb716aab939" integrity sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg== -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -2512,16 +1996,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash@^4.17.14, lodash@^4.17.19: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -2547,6 +2021,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== +lru-cache@^11.0.0: + version "11.2.6" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.2.6.tgz#356bf8a29e88a7a2945507b31f6429a65a192c58" + integrity sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ== + lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -2580,89 +2059,48 @@ memorystream@^0.3.1: resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw== -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" - integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== +minimatch@^10.2.1, minimatch@^10.2.2, minimatch@^10.2.4: + version "10.2.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.4.tgz#465b3accbd0218b8281f5301e27cedc697f96fde" + integrity sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg== dependencies: - braces "^3.0.3" - picomatch "^2.3.1" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + brace-expansion "^5.0.2" -minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== +minimatch@^3.0.3, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1: + version "3.1.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.5.tgz#580c88f8d5445f2bd6aa8f3cadefa0de79fbd69e" + integrity sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w== dependencies: brace-expansion "^1.1.7" -minimatch@^9.0.3, minimatch@^9.0.4, minimatch@^9.0.5: - version "9.0.5" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" - integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.6: - version "1.2.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" - integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== - -minipass@^3.0.0: - version "3.3.6" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.6.tgz#7bba384db3a1520d18c9c0e5251c3444e95dd94a" - integrity sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== +minimatch@^9.0.4, minimatch@^9.0.5: + version "9.0.9" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.9.tgz#9b0cb9fcb78087f6fd7eababe2511c4d3d60574e" + integrity sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg== dependencies: - yallist "^4.0.0" - -minipass@^4.0.0: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - -minipass@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" - integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== + brace-expansion "^2.0.2" -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.4, minipass@^7.1.2, minipass@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== -mkdirp@^0.5.1: - version "0.5.6" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" - integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== +minizlib@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-3.1.0.tgz#6ad76c3a8f10227c9b51d1c9ac8e30b27f5a251c" + integrity sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw== dependencies: - minimist "^1.2.6" + minipass "^7.1.2" -mkdirp@^1.0.3, mkdirp@^1.0.4: +mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mocha@^11.1.0: - version "11.7.2" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.2.tgz#3c0079fe5cc2f8ea86d99124debcc42bb1ab22b5" - integrity sha512-lkqVJPmqqG/w5jmmFtiRvtA2jkDyNVUcefFJKb2uyX4dekk8Okgqop3cgbFiaIvj8uCRJVTP5x9dfxGyXm2jvQ== +mocha@^11.7.5: + version "11.7.5" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-11.7.5.tgz#58f5bbfa5e0211ce7e5ee6128107cefc2515a627" + integrity sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig== dependencies: browser-stdout "^1.3.1" chokidar "^4.0.1" @@ -2672,6 +2110,7 @@ mocha@^11.1.0: find-up "^5.0.0" glob "^10.4.5" he "^1.2.0" + is-path-inside "^3.0.3" js-yaml "^4.1.0" log-symbols "^4.1.0" minimatch "^9.0.5" @@ -2690,24 +2129,10 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -multimatch@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-7.0.0.tgz#d0a1bf144db9106b8d19e3cb8cabec1a8986c27f" - integrity sha512-SYU3HBAdF4psHEL/+jXDKHO95/m5P2RvboHT2Y0WtTttvJLP4H/2WS9WlQPFvF6C8d6SpLw8vjCnQOnVIVOSJQ== - dependencies: - array-differ "^4.0.0" - array-union "^3.0.1" - minimatch "^9.0.3" - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - nan@^2.17.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.22.2.tgz#6b504fd029fb8f38c0990e52ad5c26772fdacfbb" - integrity sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ== + version "2.25.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.25.0.tgz#937ed345e63d9481362a7942d49c4860d27eeabd" + integrity sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g== natural-compare@^1.4.0: version "1.4.0" @@ -2729,7 +2154,7 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-pty@^1.0.0: +node-pty@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/node-pty/-/node-pty-1.0.0.tgz#7daafc0aca1c4ca3de15c61330373af4af5861fd" integrity sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA== @@ -2781,7 +2206,7 @@ npm-run-all@^4.1.5: shell-quote "^1.6.1" string.prototype.padend "^3.0.0" -object-inspect@^1.13.3: +object-inspect@^1.13.3, object-inspect@^1.13.4: version "1.13.4" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== @@ -2810,25 +2235,6 @@ once@^1.3.0, once@^1.4.0: dependencies: wrappy "1" -onetime@^5.1.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -2841,11 +2247,6 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== - own-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" @@ -2855,7 +2256,7 @@ own-keys@^1.0.1: object-keys "^1.1.1" safe-push-apply "^1.0.0" -p-all@^5.0.0: +p-all@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/p-all/-/p-all-5.0.1.tgz#688c4032f32d321fc69ef3d896aa5b2fe0b8406b" integrity sha512-LMT7WX9ZSaq3J1zjloApkIVmtz0ZdMFSIqbuiEa3txGYPLjUPOvgOPOx3nFjo+f37ZYL+1aY666I2SG7GVwLOA== @@ -2903,18 +2304,11 @@ pac-resolver@^7.0.1: degenerator "^5.0.0" netmask "^2.0.2" -package-json-from-dist@^1.0.0: +package-json-from-dist@^1.0.0, package-json-from-dist@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -2923,11 +2317,6 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-node-version@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" - integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== - path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -2961,6 +2350,14 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -2968,11 +2365,6 @@ path-type@^3.0.0: dependencies: pify "^3.0.0" -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - pathval@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" @@ -2985,20 +2377,20 @@ pause-stream@^0.0.11: dependencies: through "~2.3" -picocolors@^1.0.0, picocolors@^1.1.1: +picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== -picomatch@^2.0.4, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^2.0.4: + version "2.3.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.2.tgz#5a942915e26b372dc0f0e6753149a16e6b1c5601" + integrity "sha1-WpQpFeJrNy3A8OZ1MUmhbmscVgE= sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==" -picomatch@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" - integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== +picomatch@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" + integrity "sha1-/W9eAKFDCG4HTf/kySS4+yk7BYk= sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" pidtree@^0.3.0: version "0.3.1" @@ -3010,23 +2402,6 @@ pify@^3.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== -plugin-error@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" - integrity sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA== - dependencies: - ansi-colors "^1.0.1" - arr-diff "^4.0.0" - arr-union "^3.1.0" - extend-shallow "^3.0.2" - -plugin-error@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-2.0.1.tgz#f2ac92bac8c85e3e23492d76d0c3ca12f30eb00b" - integrity sha512-zMakqvIDyY40xHOvzXka0kUvf40nYIuwRE8dWhti2WtjQZ31xAgBZBhxsK7vK3QbRXS1Xms/LO7B5cuAsfB2Gg== - dependencies: - ansi-colors "^1.0.1" - possible-typed-array-names@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz#93e3582bc0e5426586d9d07b79ee40fc841de4ae" @@ -3037,21 +2412,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - proxy-agent@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.5.0.tgz#9e49acba8e4ee234aacb539f89ed9c23d02f232d" @@ -3086,18 +2451,6 @@ punycode@^2.1.0: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -3107,7 +2460,7 @@ read-pkg@^3.0.0: normalize-package-data "^2.3.2" path-type "^3.0.0" -readable-stream@^3.0.6, readable-stream@^3.4.0: +readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -3165,7 +2518,7 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9: get-proto "^1.0.1" which-builtin-type "^1.2.1" -regexp.prototype.flags@^1.5.3: +regexp.prototype.flags@^1.5.4: version "1.5.4" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz#1ad6c62d44a259007e55b3970e00f746efbcaa19" integrity sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA== @@ -3177,11 +2530,6 @@ regexp.prototype.flags@^1.5.3: gopd "^1.2.0" set-function-name "^2.0.2" -regexpp@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" - integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== - remove-trailing-separator@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" @@ -3197,11 +2545,6 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - resolve-options@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-2.0.0.tgz#a1a57a9949db549dd075de3f5550675f02f1e4c5" @@ -3210,66 +2553,26 @@ resolve-options@^2.0.0: value-or-function "^4.0.0" resolve@^1.10.0: - version "1.22.10" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.10.tgz#b663e83ffb09bbf2386944736baae803029b8b39" - integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== + version "1.22.11" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.11.tgz#aad857ce1ffb8bfa9b0b1ac29f1156383f68c262" + integrity sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ== dependencies: - is-core-module "^2.16.0" + is-core-module "^2.16.1" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - reusify@^1.0.4: version "1.1.0" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== -rimraf@2.6.3: - version "2.6.3" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" - integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== +rimraf@^6.1.3: + version "6.1.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.1.3.tgz#afbee236b3bd2be331d4e7ce4493bac1718981af" + integrity sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA== dependencies: - glob "^7.1.3" - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rimraf@^5.0.10: - version "5.0.10" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c" - integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ== - dependencies: - glob "^10.3.7" - -run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -rxjs@^6.6.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" + glob "^13.0.3" + package-json-from-dist "^1.0.1" safe-array-concat@^1.1.3: version "1.1.3" @@ -3282,16 +2585,16 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + safe-push-apply@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz#01850e981c1602d398c85081f360e4e6d03d27f5" @@ -3309,7 +2612,7 @@ safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3.0.0": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -3319,22 +2622,15 @@ safe-regex-test@^1.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.1.2: - version "6.3.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" - integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== - -semver@^7.3.7, semver@^7.6.0, semver@^7.7.1: - version "7.7.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58" - integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== +semver@^7.7.3, semver@^7.7.4: + version "7.7.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" + integrity sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA== -serialize-javascript@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" - integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== - dependencies: - randombytes "^2.1.0" +serialize-javascript@^6.0.2, serialize-javascript@^7.0.5: + version "7.0.5" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-7.0.5.tgz#c798cc0552ffbb08981914a42a8756e339d0d5b1" + integrity sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw== set-function-length@^1.2.2: version "1.2.2" @@ -3391,7 +2687,7 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shell-quote@^1.6.1, shell-quote@^1.8.2: +shell-quote@^1.6.1, shell-quote@^1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== @@ -3441,35 +2737,11 @@ sigmund@^1.0.1: resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha512-fCvEXfh6NWpm+YSuY2bpXb/VIihqWA6hLsgboC+0nl71Q7N7o2eaCW8mJa/NLvQhs6jpd3VZV4UiUQlV6+lc8g== -signal-exit@^3.0.2: - version "3.0.7" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - signal-exit@^4.0.1: version "4.1.0" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -slash@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-5.1.0.tgz#be3adddcdf09ac38eebe8dcdc7b1a57a75b095ce" - integrity sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg== - -slice-ansi@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" - integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== - dependencies: - ansi-styles "^3.2.0" - astral-regex "^1.0.0" - is-fullwidth-code-point "^2.0.0" - smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" @@ -3485,11 +2757,11 @@ socks-proxy-agent@^8.0.5: socks "^2.8.3" socks@^2.8.3: - version "2.8.4" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.4.tgz#07109755cdd4da03269bda4725baa061ab56d5cc" - integrity sha512-D3YaD0aRxR3mEcqnidIs7ReYJFVzWdd6fXJYUM8ixcQcJRGTka/b3saV0KflYhyVJXKhb947GndU35SxYNResQ== + version "2.8.7" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.8.7.tgz#e2fb1d9a603add75050a2067db8c381a0b5669ea" + integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== dependencies: - ip-address "^9.0.5" + ip-address "^10.0.1" smart-buffer "^4.2.0" source-map@~0.6.1: @@ -3519,9 +2791,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.21" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz#6d6e980c9df2b6fc905343a3b2d702a6239536c3" - integrity sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg== + version "3.0.23" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz#b069e687b1291a32f126893ed76a27a745ee2133" + integrity sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw== split@^1.0.1: version "1.0.1" @@ -3530,15 +2802,13 @@ split@^1.0.1: dependencies: through "2" -sprintf-js@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +stop-iteration-iterator@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz#f481ff70a548f6124d0312c3aa14cbfa7aa542ad" + integrity sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ== + dependencies: + es-errors "^1.3.0" + internal-slot "^1.1.0" stream-combiner@^0.2.2: version "0.2.2" @@ -3563,22 +2833,14 @@ stream-to-pull-stream@^1.7.3: looper "^3.0.0" pull-stream "^3.2.3" -streamfilter@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/streamfilter/-/streamfilter-3.0.0.tgz#8c61b08179a6c336c6efccc5df30861b7a9675e7" - integrity sha512-kvKNfXCmUyC8lAXSSHCIXBUlo/lhsLcCU/OmzACZYpRUdtKIH68xYhm/+HI15jFJYtNJGYtCgn2wmIiExY1VwA== - dependencies: - readable-stream "^3.0.6" - streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: - version "2.22.0" - resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.22.0.tgz#cd7b5e57c95aaef0ff9b2aef7905afa62ec6e4a7" - integrity sha512-sLh1evHOzBy/iWRiR6d1zRcLao4gGZr3C1kzNz4fopCOKJb6xD9ub8Mpi9Mr1R6id5o43S+d93fI48UC5uM9aw== + version "2.23.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.23.0.tgz#7d0f3d00d4a6c5de5728aecd6422b4008d66fd0b" + integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== dependencies: + events-universal "^1.0.0" fast-fifo "^1.3.2" text-decoder "^1.1.0" - optionalDependencies: - bare-events "^2.2.0" "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" @@ -3589,15 +2851,6 @@ streamx@^2.12.0, streamx@^2.12.5, streamx@^2.13.2, streamx@^2.14.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -3684,13 +2937,6 @@ string_decoder@~1.1.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -3699,18 +2945,18 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: ansi-regex "^5.0.1" strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + version "7.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" + integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== dependencies: - ansi-regex "^6.0.1" + ansi-regex "^6.2.2" strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== @@ -3741,27 +2987,16 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -table@^5.2.3: - version "5.4.6" - resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" - integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== - dependencies: - ajv "^6.10.2" - lodash "^4.17.14" - slice-ansi "^2.1.0" - string-width "^3.0.0" - -tar@^6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" - integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== +tar@^7.5.10: + version "7.5.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-7.5.11.tgz#1250fae45d98806b36d703b30973fa8e0a6d8868" + integrity sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ== 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" + "@isaacs/fs-minipass" "^4.0.0" + chownr "^3.0.0" + minipass "^7.1.2" + minizlib "^3.1.0" + yallist "^5.0.0" teex@^1.0.1: version "1.0.1" @@ -3771,9 +3006,9 @@ teex@^1.0.1: streamx "^2.12.5" text-decoder@^1.1.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.3.tgz#b19da364d981b2326d5f43099c310cc80d770c65" - integrity sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA== + version "1.2.7" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.2.7.tgz#5d073a9a74b9c0a9d28dfadcab96b604af57d8ba" + integrity sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ== dependencies: b4a "^1.6.4" @@ -3790,37 +3025,18 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" -through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.4: +through@2, through@^2.3.8, through@~2.3, through@~2.3.4: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== -time-stamp@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" - integrity sha512-gLCeArryy2yNTRzTGKbZbloctj64jkZ57hj5zdraXue6aFgd6PmvVtEyiUU+hvU0v7q08oVv8r8ev0tRo6bvgw== - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -to-absolute-glob@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-3.0.0.tgz#d13663608718fd44bf68fa0f034abdd4fd157eca" - integrity sha512-loO/XEWTRqpfcpI7+Jr2RR2Umaaozx1t6OSVWtMi0oy5F/Fxg3IC+D/TToDnxyAGs7uZBGT/6XmyDUxgsObJXA== +tinyglobby@^0.2.15: + version "0.2.15" + resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2" + integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ== dependencies: - is-absolute "^1.0.0" - is-negated-glob "^1.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" + fdir "^6.5.0" + picomatch "^4.0.3" to-through@^3.0.0: version "3.0.0" @@ -3829,10 +3045,10 @@ to-through@^3.0.0: dependencies: streamx "^2.12.5" -ts-api-utils@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91" - integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ== +ts-api-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz#2690579f96d2790253bdcf1ca35d569ad78f9ad8" + integrity sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA== ts-node@^10.9.2: version "10.9.2" @@ -3853,23 +3069,11 @@ ts-node@^10.9.2: v8-compile-cache-lib "^3.0.1" yn "3.1.1" -tslib@^1.8.1, tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.0.1: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -3877,33 +3081,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== - dependencies: - prelude-ls "~1.1.2" - type-detect@^4.0.0, type-detect@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - typed-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz#a72395450a4869ec033fd549371b47af3a2ee536" @@ -3957,10 +3139,10 @@ typescript-formatter@^7.2.2: commandpost "^1.0.0" editorconfig "^0.15.0" -typescript@^5.8.2: - version "5.9.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6" - integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A== +typescript@^5.9.3: + version "5.9.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.3.tgz#5b4f59e15310ab17a216f5d6cf53ee476ede670f" + integrity sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw== unbox-primitive@^1.1.0: version "1.1.0" @@ -3972,20 +3154,15 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" -unc-path-regex@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" - integrity sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg== - -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.21.0: + version "6.21.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb" + integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== -undici-types@~6.20.0: - version "6.20.0" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" - integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== +undici-types@~7.18.0: + version "7.18.2" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" + integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== untildify@^4.0.0: version "4.0.0" @@ -4009,11 +3186,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@^2.0.3: - version "2.4.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz#cdada8bec61e15865f05d097c5f4fd30e94dc128" - integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -4035,7 +3207,7 @@ vinyl-contents@^2.0.0: bl "^5.0.0" vinyl "^3.0.0" -vinyl-fs@^4.0.0: +vinyl-fs@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-4.0.2.tgz#d46557653e4a7109f29d626a9cf478680c7f8c70" integrity sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA== @@ -4122,15 +3294,16 @@ which-collection@^1.0.2: is-weakmap "^2.0.2" is-weakset "^2.0.3" -which-typed-array@^1.1.16, which-typed-array@^1.1.18: - version "1.1.18" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.18.tgz#df2389ebf3fbb246a71390e90730a9edb6ce17ad" - integrity sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA== +which-typed-array@^1.1.16, which-typed-array@^1.1.19: + version "1.1.20" + resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.20.tgz#3fdb7adfafe0ea69157b1509f3a1cd892bd1d122" + integrity sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg== dependencies: available-typed-arrays "^1.0.7" call-bind "^1.0.8" - call-bound "^1.0.3" - for-each "^0.3.3" + call-bound "^1.0.4" + for-each "^0.3.5" + get-proto "^1.0.1" gopd "^1.2.0" has-tostringtag "^1.0.2" @@ -4148,15 +3321,15 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.5, word-wrap@~1.2.3: +word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== workerpool@^9.2.0: - version "9.3.2" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.2.tgz#4c045a8b437ae1bc70c646af11929a8b4d238656" - integrity sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A== + version "9.3.4" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.3.4.tgz#f6c92395b2141afd78e2a889e80cb338fe9fca41" + integrity sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg== "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" @@ -4190,13 +3363,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -write@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" - integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== - dependencies: - mkdirp "^0.5.1" - xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -4212,10 +3378,10 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yallist@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533" + integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw== yargs-parser@^20.2.2: version "20.2.9"